----
url: https://angular.dev/guide/i18n/locale-id
----

Angular uses the Unicode *locale identifier* (Unicode locale ID) to find the correct locale data for internationalization of text strings.

### Unicode locale ID

* A locale ID conforms to the [Unicode Common Locale Data Repository (CLDR) core specification](https://cldr.unicode.org/index/cldr-spec "Core Specification | Unicode CLDR Project"). For more information about locale IDs, see [Unicode Language and Locale Identifiers](https://cldr.unicode.org/index/cldr-spec/picking-the-right-language-code "Unicode Language and Locale Identifiers - Core Specification | Unicode CLDR Project").

* CLDR and Angular use [BCP 47 tags](https://www.rfc-editor.org/info/bcp47 "BCP 47 | RFC Editor") as the base for the locale ID

A locale ID specifies the language, country, and an optional code for further variants or subdivisions. A locale ID consists of the language identifier, a hyphen (`-`) character, and the locale extension.

```
{language_id}-{locale_extension}
```

**HELPFUL:** To accurately translate your Angular project, you must decide which languages and locales you are targeting for internationalization.

Many countries share the same language, but differ in usage. The differences include grammar, punctuation, formats for currency, decimal numbers, dates, and so on.

For the examples in this guide, use the following languages and locales.

| Language | Locale                   | Unicode locale ID |
| :------- | :----------------------- | :---------------- |
| English  | Canada                   | `en-CA`           |
| English  | United States of America | `en-US`           |
| French   | Canada                   | `fr-CA`           |
| French   | France                   | `fr-FR`           |

The [Angular repository](https://github.com/angular/angular/tree/main/packages/common/locales "angular/packages/common/locales | angular/angular | GitHub") includes common locales.

###

For a list of language codes, see [ISO 639-2](https://www.loc.gov/standards/iso639-2).

## [Set the source locale ID](#set-the-source-locale-id)

Use the Angular CLI to set the source language in which you are writing the component template and code.

By default, Angular uses `en-US` as the source locale of your project.

To change the source locale of your project for the build, complete the following actions.

1. Open the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file.
2. Add or modify the `sourceLocale` field inside the `i18n` section:

```
{
  "projects": {
    "your-project": {
      "i18n": {
        "sourceLocale": "ca" // Use your desired locale code
      }
    }
  }
}
```

## [What's next](#whats-next)

----
url: https://angular.dev/api/core/RendererFactory2
----

```
    
      abstract class RendererFactory2 {}
    
    
```

Creates and initializes a custom renderer for a host DOM element.

@paramhostElement`any`

The element to render.

@paramtype`RendererType2 | null`

The base class to implement.

@returns`Renderer2`

The new custom renderer instance.

A callback invoked when rendering has begun.

@returns`void`

A callback invoked when rendering has completed.

@returns`void`

Use with animations test-only mode. Notifies the test when rendering has completed.

@returns`Promise<any>`

The asynchronous result of the developer-defined function.

Jump to details

----
url: https://angular.dev/api/core/ResourceLoaderParams
----

```
    
      interface ResourceLoaderParams<R> {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/forms/signals/dynamic-forms-with-json
----

Some forms can't define their structure at compile time. Server-driven forms, admin panels, multi-tenant applications, and CMS-managed content all need to render fields from configuration delivered at runtime, typically as JSON from a backend, an admin tool, or per-tenant settings.

This guide shows how to build forms whose model, schema, validation, and rendering are all derived from a single runtime configuration.

## [When to use JSON-driven forms](#when-to-use-json-driven-forms)

This pattern is a good choice when:

* A backend defines which fields appear based on user role, feature flags, or business rules
* Non-developers configure form structure through an admin panel or CMS
* Each tenant in a multi-tenant application has its own form structure stored as configuration
* Forms need to evolve without redeploying the frontend

Use a static form (with fields defined directly in your component) when the structure is known at build time. Static forms get full TypeScript checking on every field, plus straightforward testing and tooling support.

## [Defining a typed field config](#defining-a-typed-field-config)

When you want to render fields from runtime configuration, start with a TypeScript type that captures the shape of each field. A discriminated union by `kind` lets each variant declare its own validation options:

```
type FieldConfig =
  | {kind: 'text'; name: string; label: string; required?: boolean}
  | {kind: 'number'; name: string; label: string; required?: boolean; min?: number; max?: number};
```

Every variant has a name, label, and optional `required` flag. Number fields additionally accept `min` and `max` bounds. Add new variants by adding new `kind` branches.

A concrete config might look like this:

```
const profileConfig: FieldConfig[] = [
  {kind: 'text', name: 'fullName', label: 'Full Name', required: true},
  {kind: 'number', name: 'age', label: 'Age', required: true, min: 18, max: 120},
];
```

In practice this `FieldConfig[]` typically arrives from your backend, an admin panel, or a CMS. For brevity, the examples below use an in-component literal.

## [Building the model from config](#building-the-model-from-config)

The form's model needs one entry per field with a default value matching the field's kind. A small helper handles this:

```
function buildModel(configs: FieldConfig[]): Record<string, string | number | null> {
  const initial: Record<string, string | number | null> = {};
  for (const config of configs) {
    initial[config.name] = config.kind === 'number' ? null : '';
  }
  return initial;
}
```

The model uses `Record<string, string | number | null>` because the keys are not known ahead of time.

In addition, numeric fields initialize to `null` rather than `0` so an empty field reads as empty. With `0`, [`required()`](api/forms/signals/required) would treat the field as already filled, and any [`min()`](api/forms/signals/min) constraint above zero would flag the field invalid before the user enters anything.

## [Building the schema from config](#building-the-schema-from-config)

The schema is also derived from the config. You can loop through each entry and apply the validators that match its kind:

```
import {required, min, max, SchemaFn, SchemaPath} from '@angular/forms/signals';

function buildSchema(configs: FieldConfig[]): SchemaFn<Record<string, string | number | null>> {
  return (path) => {
    for (const config of configs) {
      const fieldPath = path[config.name];

      if (config.required) {
        required(fieldPath);
      }

      if (config.kind === 'number') {
        const numberPath = fieldPath as unknown as SchemaPath<number | null>;
        if (config.min !== undefined) min(numberPath, config.min);
        if (config.max !== undefined) max(numberPath, config.max);
      }
    }
  };
}
```

The discriminated union narrows `config` inside each branch, so `config.min` and `config.max` are typed correctly when `config.kind === 'number'`.

## [Expressing conditional rules in config](#expressing-conditional-rules-in-config)

Some validation rules only make sense under certain conditions. For example, US state codes need validating only when the country is the US. Express these dependencies in the config by adding a `when` discriminator that names another field and the value it must equal:

```
type WhenCondition = {field: string; equals: string | number};

type FieldConfig =
  | {kind: 'text'; name: string; label: string; required?: boolean; when?: WhenCondition}
  | {
      kind: 'number';
      name: string;
      label: string;
      required?: boolean;
      min?: number;
      max?: number;
      when?: WhenCondition;
    };
```

Update `buildSchema()` to translate `when` into an [`applyWhen()`](api/forms/signals/applyWhen) call. Shared rule application logic moves into a small closure so the conditional and unconditional branches both call the same function:

```
import {applyWhen, required, min, max, SchemaFn, SchemaPath} from '@angular/forms/signals';

function buildSchema(configs: FieldConfig[]): SchemaFn<Record<string, string | number | null>> {
  return (rootPath) => {
    for (const config of configs) {
      const applyRules = (path: typeof rootPath) => {
        const fieldPath = path[config.name];
        if (config.required) required(fieldPath);
        if (config.kind === 'number') {
          const numberPath = fieldPath as unknown as SchemaPath<number | null>;
          if (config.min !== undefined) min(numberPath, config.min);
          if (config.max !== undefined) max(numberPath, config.max);
        }
      };

      if (config.when) {
        const {field, equals} = config.when;
        applyWhen(rootPath, ({valueOf}) => valueOf(rootPath[field]) === equals, applyRules);
      } else {
        applyRules(rootPath);
      }
    }
  };
}
```

When [`applyWhen()`](/api/forms/signals/applyWhen)'s condition is true, the rules inside activate. When the condition becomes false, the rules deactivate and the field's validation state clears. Because the condition function reads through `valueOf(rootPath[field])`, the form re-evaluates the gate every time the referenced field changes.

A config that uses `when` looks like this:

```
const addressConfig: FieldConfig[] = [
  {kind: 'text', name: 'country', label: 'Country', required: true},
  {
    kind: 'text',
    name: 'stateCode',
    label: 'State',
    required: true,
    when: {field: 'country', equals: 'US'},
  },
];
```

The `stateCode` field only requires a value when `country` is `'US'`. Users entering any other country can leave `stateCode` blank without blocking submission.

For more complex conditions (multiple fields, ranges, or non-equality checks), extend `WhenCondition` with additional discriminators (such as `in: string[]` or `notEquals: string | number`) and translate each variant inside `buildSchema()`. The principle is the same: the config carries the data, `buildSchema()` translates it to [`applyWhen()`](/api/forms/signals/applyWhen) calls.

To gate visibility instead of validation, follow the same pattern using [`hidden()`](api/forms/signals/hidden) on the field path. See [Configuring `hidden()` state on fields](guide/forms/signals/form-logic#configuring-hidden-state-on-fields) for details.

## [Expressing repeating fields in config](#expressing-repeating-fields-in-config)

Some configurations need fields that grow and shrink at runtime, like a list of phone numbers, tags, or invoice line items. Add an `array` kind to the config and translate it to [`applyEach()`](api/forms/signals/applyEach) so per-item rules apply uniformly as items come and go.

Extend `FieldConfig` with an `array` variant. This example uses arrays of strings; the same approach scales to arrays of objects by replacing the item shape with a record:

```
type FieldConfig =
  | {kind: 'text'; name: string; label: string; required?: boolean; when?: WhenCondition}
  | {
      kind: 'number';
      name: string;
      label: string;
      required?: boolean;
      min?: number;
      max?: number;
      when?: WhenCondition;
    }
  | {kind: 'array'; name: string; label: string; itemRequired?: boolean; when?: WhenCondition};
```

Update `buildModel()` to initialize array fields with an empty array. The model widens to include `string[]`:

```
function buildModel(configs: FieldConfig[]): Record<string, string | number | null | string[]> {
  const initial: Record<string, string | number | null | string[]> = {};
  for (const config of configs) {
    if (config.kind === 'number') initial[config.name] = null;
    else if (config.kind === 'array') initial[config.name] = [];
    else initial[config.name] = '';
  }
  return initial;
}
```

Update `buildSchema()` to apply per-item rules with [`applyEach()`](/api/forms/signals/applyEach). The path coming from a `Record<string, string | number | null | string[]>` model is too broad for [`applyEach()`](/api/forms/signals/applyEach) (and for `min()` / `max()`) to type-check directly, so cast `fieldPath` to the appropriate shape inside each `kind` branch:

```
import {
  applyEach,
  applyWhen,
  required,
  min,
  max,
  SchemaFn,
  SchemaPath,
} from '@angular/forms/signals';

function buildSchema(
  configs: FieldConfig[],
): SchemaFn<Record<string, string | number | null | string[]>> {
  return (rootPath) => {
    for (const config of configs) {
      const applyRules = (path: typeof rootPath) => {
        const fieldPath = path[config.name];

        if (config.kind === 'array') {
          const arrayPath = fieldPath as unknown as SchemaPath<string[]>;
          if (config.itemRequired) {
            applyEach(arrayPath, (item) => required(item));
          }
          return;
        }

        if (config.required) required(fieldPath);

        if (config.kind === 'number') {
          const numberPath = fieldPath as unknown as SchemaPath<number | null>;
          if (config.min !== undefined) min(numberPath, config.min);
          if (config.max !== undefined) max(numberPath, config.max);
        }
      };

      if (config.when) {
        const {field, equals} = config.when;
        applyWhen(rootPath, ({valueOf}) => valueOf(rootPath[field]) === equals, applyRules);
      } else {
        applyRules(rootPath);
      }
    }
  };
}
```

The casts inside each branch are deliberate escape hatches: you're trading the compiler's structural guarantee for a runtime invariant the surrounding `kind` check enforces. Each cast is scoped to a `kind` block, so the assumption stays local and straightforward to audit.

A config that uses the `array` kind looks like this:

```
const contactConfig: FieldConfig[] = [
  {kind: 'text', name: 'fullName', label: 'Full name', required: true},
  {kind: 'array', name: 'phoneNumbers', label: 'Phone numbers', itemRequired: true},
];
```

To render an array field, iterate it with `@for` and let users add or remove items by updating the model signal. Add a typed accessor that returns a [`FieldTree`](api/forms/signals/FieldTree) so the iteration sees the array structure, and define methods to grow and shrink the model:

```
import {FieldTree} from '@angular/forms/signals';

// inside the component class
asArrayField(name: string): FieldTree<string[]> {
  return this.dynamicForm[name] as unknown as FieldTree<string[]>;
}

addItem(name: string) {
  this.model.update(current => ({
    ...current,
    [name]: [...(current[name] as string[]), ''],
  }));
}

removeItem(name: string, index: number) {
  this.model.update(current => ({
    ...current,
    [name]: (current[name] as string[]).filter((_, i) => i !== index),
  }));
}
```

`FieldTree<string[]>` is iterable, so `@for` can walk it; each item is a `FieldTree<string>` that satisfies `[formField]` directly. Leaf fields can use `Field<T>` accessors instead, since `Field<T>` is the callable signature without iteration.

In the template, render an array case like this:

```
@case ('array') {
  <fieldset>
    <legend>{{ config.label }}</legend>
    @for (item of asArrayField(config.name); track item) {
      <input type="text" [formField]="item" />
      <button type="button" (click)="removeItem(config.name, $index)">Remove</button>
    }
    <button type="button" (click)="addItem(config.name)">Add</button>
  </fieldset>
}
```

The `addItem()` method extends the model; the form re-derives the array's fields automatically. New items start with fresh validation state. `removeItem()` filters the model; the dropped item's field state goes with it.

### [Tracking item identity](#tracking-item-identity)

Signal Forms tracks each item in an array of objects by its identity. When you store a reference to a field at a specific position, that reference follows the underlying data, not the position. Reading state through the held reference returns the data even if it has moved:

```
const contactModel = signal([
  {name: 'Alice', phone: '555-0001'},
  {name: 'Bob', phone: '555-0002'},
]);

const contactForm = form(contactModel);

// Hold a reference to the field that's currently at index 0 (Alice).
const aliceField = contactForm[0];

// Swap the array items so Bob is at index 0, Alice at index 1.
contactModel.update(([alice, bob]) => [bob, alice]);

// The held reference still points to Alice's field, even after the swap.
console.log(aliceField().value().phone); // '555-0001' (Alice's number)
console.log(contactForm[0]().value().phone); // '555-0002' (Bob, now at index 0)
```

This identity tracking prevents bugs when sorting, reordering, or filtering, as long as the referenced item remains in the array. Stored field references remain valid even when array order changes; removing the referenced item itself orphans the held reference.

For arrays of primitives (the `phoneNumbers` example above), Signal Forms tracks items positionally instead: index 0 always refers to whatever value is currently at position 0.

Identity here is by JavaScript object reference, not by a logical id like a database key. If you replace the array with freshly-deserialized objects (for example, after a server reload), field state doesn't follow the logical item even when each item's `id` is unchanged. The guarantee covers in-memory operations like sorting, reordering, and filtering, not data refresh.

## [Validating the config](#validating-the-config)

Configs from external sources need validation before the form is built. Several failure modes can hide in untrusted JSON:

* Duplicate `name` values overwrite earlier model entries and break the `track config.name` expression in the template.
* A `when` clause that names a non-existent field fails at runtime when the condition first evaluates.
* A `when` clause that compares against an `array` field has no defined equality semantics.
* A `when.equals` value whose type doesn't match the referenced field's kind silently never matches, hiding the conditional behavior as if the rule were never active.

Catch all four at the boundary:

```
function validateConfigs(configs: FieldConfig[]): FieldConfig[] {
  const knownNames = new Set<string>();

  for (const config of configs) {
    if (knownNames.has(config.name)) {
      throw new Error(`Duplicate field name in config: "${config.name}"`);
    }
    knownNames.add(config.name);
  }

  for (const config of configs) {
    if (!config.when) continue;
    if (!knownNames.has(config.when.field)) {
      throw new Error(
        `Field "${config.name}" references unknown field "${config.when.field}" in its 'when' condition.`,
      );
    }
    const referenced = configs.find((c) => c.name === config.when!.field)!;
    if (referenced.kind === 'array') {
      throw new Error(
        `Field "${config.name}" cannot use 'when' to compare against array field "${config.when.field}".`,
      );
    }
    const expected = referenced.kind === 'text' ? 'string' : 'number';
    if (typeof config.when.equals !== expected) {
      throw new Error(
        `Field "${config.name}" compares ${referenced.kind} field "${config.when.field}" against a ${typeof config.when.equals} value; expected a ${expected}.`,
      );
    }
  }

  return configs;
}
```

The first pass enforces uniqueness; the second pass walks each `when` clause to confirm the referenced field exists, isn't an array, and is being compared against a value of the right type. The function returns the configs unchanged on success, so it composes cleanly with the field initializer that holds the configs in the component. Failures surface at the boundary between your application and the upstream source rather than as opaque form misbehavior later.

## [Rendering the form dynamically](#rendering-the-form-dynamically)

In the component, use `@for` to iterate the configs and `@switch` on `kind` to pick the right input control:

```
import {Component, signal} from '@angular/core';
import {Field, FieldTree, form, FormField, FormRoot} from '@angular/forms/signals';

@Component({
  selector: 'app-dynamic-form',
  imports: [FormField, FormRoot],
  template: `
    <form [formRoot]="dynamicForm">
      @for (config of configs; track config.name) {
        @switch (config.kind) {
          @case ('text') {
            <label>
              {{ config.label }}
              <input type="text" [formField]="asTextField(config.name)" />
            </label>
          }
          @case ('number') {
            <label>
              {{ config.label }}
              <input type="number" [formField]="asNumberField(config.name)" />
            </label>
          }
          @case ('array') {
            <fieldset>
              <legend>{{ config.label }}</legend>
              @for (item of asArrayField(config.name); track item; let i = $index) {
                <input type="text" [formField]="item" />
                <button type="button" (click)="removeItem(config.name, i)">Remove</button>
              }
              <button type="button" (click)="addItem(config.name)">Add</button>
            </fieldset>
          }
        }
      }
    </form>
  `,
})
export class DynamicForm {
  configs: FieldConfig[] = validateConfigs([
    {kind: 'text', name: 'fullName', label: 'Full Name', required: true},
    {kind: 'number', name: 'age', label: 'Age', required: true, min: 18, max: 120},
    {kind: 'array', name: 'phoneNumbers', label: 'Phone numbers', itemRequired: true},
  ]);

  model = signal(buildModel(this.configs));

  dynamicForm = form(this.model, buildSchema(this.configs));

  asTextField(name: string): Field<string> {
    // <input type="text"> requires Field<string>.
    return this.dynamicForm[name] as unknown as Field<string>;
  }

  asNumberField(name: string): Field<number | null> {
    // <input type="number"> requires Field<number | null>.
    return this.dynamicForm[name] as unknown as Field<number | null>;
  }

  asArrayField(name: string): FieldTree<string[]> {
    // FieldTree (not Field) so @for can iterate the array.
    return this.dynamicForm[name] as unknown as FieldTree<string[]>;
  }

  addItem(name: string) {
    this.model.update((current) => ({
      ...current,
      [name]: [...(current[name] as string[]), ''],
    }));
  }

  removeItem(name: string, index: number) {
    this.model.update((current) => ({
      ...current,
      [name]: (current[name] as string[]).filter((_, i) => i !== index),
    }));
  }
}
```

Template type-checking treats `dynamicForm[name]` as an independent expression, so the `@switch` narrowing on `config.kind` doesn't reach the indexed access. The accessors restate that narrowing as a cast at the binding site, and the matching `kind` branch guarantees the narrowed type is correct at runtime.

Because the model and schema are both derived from the same `FieldConfig[]` at component construction, they can't drift apart for a given config. The example above assumes the config is available synchronously when the component is created.

## [Next steps](#next-steps)

JSON-driven forms keep their model and schema aligned by deriving both from the same `FieldConfig[]`. Each extension in this guide (conditional rules, repeating fields) widens the type and adds a translation step inside `buildSchema()` while preserving that alignment. The model and schema stay locked together, regardless of where the config comes from or how it grows.

For related guides cover other aspects of Signal Forms, check out:

For detailed API documentation, see:

* [`form()`](api/forms/signals/form) - Create a form from a model signal
* [`applyWhen()`](api/forms/signals/applyWhen) - Apply a schema conditionally based on reactive state
* [`applyEach()`](api/forms/signals/applyEach) - Apply a schema to each item in an array field
* [`FieldTree`](api/forms/signals/FieldTree) - Navigable tree of fields exposed by [`form()`](/api/forms/signals/form)
* [`SchemaFn`](api/forms/signals/SchemaFn) - Type signature for schema functions

----
url: https://angular.dev/api/cdk/testing/protractor/ProtractorHarnessEnvironmentOptions
----

```
    
      interface ProtractorHarnessEnvironmentOptions {}
    
    
```

The query function used to find DOM elements.

Jump to details

----
url: https://angular.dev/api/common/getLocaleTimeFormat
----

Angular recommends relying on the `Intl` API for i18n. Use `Intl.DateTimeFormat` for date formating instead.

## [API](/api/common/getLocaleTimeFormat#api)

```
function getLocaleTimeFormat(locale: string, width: FormatWidth): string;
```

----
url: https://angular.dev/api/router/CanLoad
----

Use [`CanMatch`](/api/router/CanMatch) instead

## [API](#api)

```
    
      interface CanLoad {}
    
    
```

@paramroute`Route`

@paramsegments`UrlSegment[]`

@returns`MaybeAsync<GuardResult>`

## [Description](#description)

Interface that a class can implement to be a guard deciding if children can be loaded. If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), current navigation is cancelled and a new navigation starts to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements a [`CanLoad`](/api/router/CanLoad) function that decides whether the current user has permission to load requested child routes.

```
class UserToken {}
class Permissions {
  canLoadChildren(user: UserToken, id: string, segments: UrlSegment[]): boolean {
    return true;
  }
}

@Injectable()
class CanLoadTeamSection implements CanLoad {
  private readonly permissions = inject(Permissions);
  private readonly currentUser = inject(UserToken);

  canLoad(route: Route, segments: UrlSegment[]): Observable<boolean>|Promise<boolean>|boolean {
    return this.permissions.canLoadChildren(this.currentUser, route, segments);
  }
}
```

Here, the defined guard function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'team/:id',
        component: TeamComponent,
        loadChildren: () => import('./team').then(mod => mod.TeamModule),
        canLoad: [CanLoadTeamSection]
      }
    ])
  ],
  providers: [CanLoadTeamSection, UserToken, Permissions]
})
class AppModule {}
```

Jump to details

----
url: https://angular.dev/tutorials/first-app/03-HousingLocation
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/02-Home)[](/tutorials/first-app/04-interfaces)

[](/tutorials/first-app/02-Home)[](/tutorials/first-app/04-interfaces)

This tutorial lesson demonstrates how to add the `HousingLocation` component to your Angular app.

[](https://www.youtube.com/watch?v=R0nRX8jD2D0\&autoplay=1)

## [What you'll learn](#what-youll-learn)

* Your app has a new component: `HousingLocation` and it displays a message confirming that the component was added to your application.

1. ### [Create the `HousingLocation`](#create-the-housinglocation)

   In this step, you create a new component for your app.

   In the **Terminal** pane of your IDE:

   1. In your project directory, navigate to the `first-app` directory.

   2. Run this command to create a new `HousingLocation`

      ```
      ng generate component housingLocation
      ```

   3. Run this command to build and serve your app.

      ```
      ng serve
      ```

      **NOTE:** This step is only for your local environment!

   4. Open a browser and navigate to `http://localhost:4200` to find the application.

   5. Confirm that the app builds without error.

      **HELPFUL:** It should render the same as it did in the previous lesson because even though you added a new component, you haven't included it in any of the app's templates, yet.

   6. Leave `ng serve` running as you complete the next steps.

2. ### [Add the new component to your app's layout](#add-the-new-component-to-your-apps-layout)

   In this step, you add the new component, `HousingLocation` to your app's `Home`, so that it displays in your app's layout.

   In the **Edit** pane of your IDE:

   1. Open `home.ts` in the editor.

   2. In `home.ts`, import `HousingLocation` by adding this line to the file level imports.

      ### Import HousingLocation in src/app/home/home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {}
      ```

   3. Next update the `imports` property of the [`@Component`](/api/core/Component) metadata by adding `HousingLocation` to the array.

      ### Add HousingLocation to imports array in src/app/home/home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {}
      ```

   4. Now the component is ready for use in the template for the `Home`. Update the `template` property of the [`@Component`](/api/core/Component) metadata to include a reference to the `<app-housing-location>` tag.

      ### Add housing location to the component template in src/app/home/home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {}
      ```

3. ### [Add the styles for the component](#add-the-styles-for-the-component)

   In this step, you will copy over the pre-written styles for the `HousingLocation` to your app so that the app renders properly.

   1. Open `src/app/housing-location/housing-location.css`, and paste the styles below into the file:

      **NOTE:** In the browser, these can go in `src/app/housing-location/housing-location.ts` in the `styles` array.

      ### Add CSS styles to housing location to the component in src/app/housing-location/housing-location.css

      ```
      .listing {
        background: var(--accent-color);
        border-radius: 30px;
        padding-bottom: 30px;
      }
      .listing-heading {
        color: var(--primary-color);
        padding: 10px 20px 0 20px;
      }
      .listing-photo {
        height: 250px;
        width: 100%;
        object-fit: cover;
        border-radius: 30px 30px 0 0;
      }
      .listing-location {
        padding: 10px 20px 20px 20px;
      }
      .listing-location::before {
        content: url('/public/location-pin.svg') / '';
      }

      section.listing a {
        padding-left: 20px;
        text-decoration: none;
        color: var(--primary-color);
      }
      section.listing a::after {
        content: '\203A';
        margin-left: 5px;
      }
      ```

   2. Save your code, return to the browser and confirm that the app builds without error. You should find the message "housing-location works!" rendered to the screen.Correct any errors before you continue to the next step.

**SUMMARY:** In this lesson, you created a new component for your app and added it to the app's layout.

----
url: https://angular.dev/api/core/StateKey
----

```
    
      type StateKey<T> = string & {  __not_a_string: never;  __value_type?: T;}
    
    
```

## [Description](#description)

A type-safe key to use with [`TransferState`](/api/core/TransferState).

Example:

```
const COUNTER_KEY = makeStateKey<number>('counter');
let value = 10;

transferState.set(COUNTER_KEY, value);
```

Jump to details

----
url: https://angular.dev/api/core/ViewRef
----

```
    
      abstract class ViewRef extends ChangeDetectorRef {}
    
    
```

Jump to details

----
url: https://angular.dev/api/animations/AnimationStyleMetadata
----

```
    
      interface AnimationStyleMetadata extends AnimationMetadata {}
    
    
```

A set of CSS style properties.

A percentage of the total animate time at which the style is to be applied.

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragEnter
----

```
    
      interface CdkDragEnter<T = any, I = T> {}
    
    
```

Container into which the user has moved the item.

Item that was moved into the container.

Index at which the item has entered the container.

Jump to details

----
url: https://angular.dev/api/common/http/HttpDownloadProgressEvent
----

```
    
      interface HttpDownloadProgressEvent extends HttpProgressEvent {}
    
    
```

The partial response body as downloaded so far.

Only present if the responseType was `text`.

Number of bytes uploaded or downloaded.

Total number of bytes to upload or download. Depending on the request or response, this may not be computable and thus may not be present.

Jump to details

----
url: https://angular.dev/ecosystem/rxjs-interop
----

The `@angular/core/rxjs-interop` package offers APIs that help you integrate RxJS and Angular signals.

## [Create a signal from an RxJS Observable with `toSignal`](/ecosystem/rxjs-interop#create-a-signal-from-an-rxjs-observable-with-tosignal)

Use the [`toSignal`](/api/core/rxjs-interop/toSignal) function to create a signal which tracks the value of an Observable. It behaves similarly to the `async` pipe in templates, but is more flexible and can be used anywhere in an application.

```
import {Component} from '@angular/core';
import {AsyncPipe} from '@angular/common';
import {interval} from 'rxjs';
import {toSignal} from '@angular/core/rxjs-interop';

@Component({
  template: `{{ counter() }}`,
})
export class Ticker {
  counterObservable = interval(1000);

  // Get a `Signal` representing the `counterObservable`'s value.
  counter = toSignal(this.counterObservable, {initialValue: 0});
}
```

Like the `async` pipe, [`toSignal`](/api/core/rxjs-interop/toSignal) subscribes to the Observable immediately, which may trigger side effects. The subscription created by [`toSignal`](/api/core/rxjs-interop/toSignal) automatically unsubscribes from the given Observable when the component or service which calls [`toSignal`](/api/core/rxjs-interop/toSignal) is destroyed.

**IMPORTANT:** [`toSignal`](/api/core/rxjs-interop/toSignal) creates a subscription. You should avoid calling it repeatedly for the same Observable, and instead reuse the signal it returns.

### [Injection context](/ecosystem/rxjs-interop#injection-context)

[`toSignal`](/api/core/rxjs-interop/toSignal) by default needs to run in an [injection context](guide/di/dependency-injection-context), such as during construction of a component or service. If an injection context is not available, you can manually specify the [`Injector`](/api/core/Injector) to use instead.

### [Initial values](/ecosystem/rxjs-interop#initial-values)

Observables may not produce a value synchronously on subscription, but signals always require a current value. There are several ways to deal with this "initial" value of [`toSignal`](/api/core/rxjs-interop/toSignal) signals.

#### [The `initialValue` option](/ecosystem/rxjs-interop#the-initialvalue-option)

As in the example above, you can specify an `initialValue` option with the value the signal should return before the Observable emits for the first time.

#### [`undefined` initial values](/ecosystem/rxjs-interop#undefined-initial-values)

If you don't provide an `initialValue`, the resulting signal will return `undefined` until the Observable emits. This is similar to the `async` pipe's behavior of returning `null`.

#### [The `requireSync` option](/ecosystem/rxjs-interop#the-requiresync-option)

Some Observables are guaranteed to emit synchronously, such as `BehaviorSubject`. In those cases, you can specify the `requireSync: true` option.

When `requireSync` is `true`, [`toSignal`](/api/core/rxjs-interop/toSignal) enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required.

### [`manualCleanup`](/ecosystem/rxjs-interop#manualcleanup)

By default, [`toSignal`](/api/core/rxjs-interop/toSignal) automatically unsubscribes from the Observable when the component or service that creates it is destroyed.

To override this behavior, you can pass the `manualCleanup` option. You can use this setting for Observables that complete themselves naturally.

#### [Custom equality comparison](/ecosystem/rxjs-interop#custom-equality-comparison)

Some observables may emit values that are **equals** even though they differ by reference or minor detail. The `equal` option lets you define a **custom equal function** to determine when two consecutive values should be considered the same.

When two emitted values are considered equal, the resulting signal **does not update**. This prevents redundant computations, DOM updates, or effects from re-running unnecessarily.

```
import {Component} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {interval, map} from 'rxjs';

@Component(/* ... */)
export class EqualExample {
  temperature$ = interval(1000).pipe(
    map(() => ({temperature: Math.floor(Math.random() * 3) + 20})), // 20, 21, or 22 randomly
  );

  // Only update if the temperature changes
  temperature = toSignal(this.temperature$, {
    initialValue: {temperature: 20},
    equal: (prev, curr) => prev.temperature === curr.temperature,
  });
}
```

### [Error and Completion](/ecosystem/rxjs-interop#error-and-completion)

If an Observable used in [`toSignal`](/api/core/rxjs-interop/toSignal) produces an error, that error is thrown when the signal is read.

If an Observable used in [`toSignal`](/api/core/rxjs-interop/toSignal) completes, the signal continues to return the most recently emitted value before completion.

## [Create an RxJS Observable from a signal with `toObservable`](/ecosystem/rxjs-interop#create-an-rxjs-observable-from-a-signal-with-toobservable)

Use the [`toObservable`](/api/core/rxjs-interop/toObservable) utility to create an `Observable` which tracks the value of a signal. The signal's value is monitored with an [`effect`](/api/core/effect) which emits the value to the Observable when it changes.

```
import {Component, signal} from '@angular/core';
import {toObservable} from '@angular/core/rxjs-interop';

@Component(/* ... */)
export class SearchResults {
  query: Signal<string> = inject(QueryService).query;
  query$ = toObservable(this.query);

  results$ = this.query$.pipe(switchMap((query) => this.http.get('/search?q=' + query)));
}
```

As the `query` signal changes, the `query$` Observable emits the latest query and triggers a new HTTP request.

### [Injection context](/ecosystem/rxjs-interop#injection-context-to-observable)

[`toObservable`](/api/core/rxjs-interop/toObservable) by default needs to run in an [injection context](guide/di/dependency-injection-context), such as during construction of a component or service. If an injection context is not available, you can manually specify the [`Injector`](/api/core/Injector) to use instead.

### [Timing of `toObservable`](/ecosystem/rxjs-interop#timing-of-toobservable)

[`toObservable`](/api/core/rxjs-interop/toObservable) uses an effect to track the value of the signal in a `ReplaySubject`. On subscription, the first value (if available) may be emitted synchronously, and all subsequent values will be asynchronous.

Unlike Observables, signals never provide a synchronous notification of changes. Even if you update a signal's value multiple times, [`toObservable`](/api/core/rxjs-interop/toObservable) will only emit the value after the signal stabilizes.

```
const obs$ = toObservable(mySignal);
obs$.subscribe((value) => console.log(value));

mySignal.set(1);
mySignal.set(2);
mySignal.set(3);
```

Here, only the last value (3) will be logged.

## [Using `rxResource` for async data](/ecosystem/rxjs-interop#using-rxresource-for-async-data)

Angular's [`resource` function](/guide/signals/resource) gives you a way to incorporate async data into your application's signal-based code. Building on top of this pattern, [`rxResource`](/api/core/rxjs-interop/rxResource) lets you define a resource where the source of your data is defined in terms of an RxJS `Observable`. Instead of accepting a `loader` function, [`rxResource`](/api/core/rxjs-interop/rxResource) accepts a `stream` function that returns an RxJS `Observable`.

```
import {Component, inject} from '@angular/core';
import {rxResource} from '@angular/core/rxjs-interop';

@Component(/* ... */)
export class UserProfile {
  // This component relies on a service that exposes data through an RxJS Observable.
  private userData = inject(MyUserDataClient);

  protected userId = input<string>();

  private userResource = rxResource({
    params: () => ({userId: this.userId()}),

    // The `stream` property expects a factory function that returns
    // a data stream as an RxJS Observable.
    stream: ({params}) => this.userData.load(params.userId),
  });
}
```

The `stream` property accepts a factory function for an RxJS `Observable`. This factory function is passed the resource's `params` value and returns an `Observable`. The resource calls this factory function every time the `params` computation produces a new value. See [Resource loaders](/guide/signals/resource#resource-loaders) for more details on the parameters passed to the factory function.

In all other ways, [`rxResource`](/api/core/rxjs-interop/rxResource) behaves like and provides the same APIs as [`resource`](/api/core/resource) for specifying parameters, reading values, checking loading state, and examining errors.

The `Observable` returned from `stream` must always emit a value or an error before it completes; see [`NG0991`](/errors/NG0991) for what happens otherwise and how to avoid it.

----
url: https://angular.dev/guide/aria/multiselect
----

## [Overview](/guide/aria/multiselect#overview)

The multiselect pattern combines a read-only combobox trigger with a multi-select listbox popup to create highly accessible multiple-selection dropdowns with keyboard navigation and screen reader support.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="icons-material"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The icon that is displayed in the combobox. */
  readonly displayIcon = computed(() => {
    const values = this.selectedValues();
    const label = this.labels.find((label) => label.value === values[0]);
    return label ? label.icon : '';
  });

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 3rem;
  padding: 0 3.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 13rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="icons-retro"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The icon that is displayed in the combobox. */
  readonly displayIcon = computed(() => {
    const values = this.selectedValues();
    const label = this.labels.find((label) => label.value === values[0]);
    return label ? label.icon : '';
  });

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 4.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 22rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

## [Usage](/guide/aria/multiselect#usage)

The multiselect pattern works best when users need to choose multiple related items from a familiar set of options.

Consider using this pattern when:

* **Users need multiple selections** - Tags, categories, filters, or labels where multiple choices apply
* **The option list is fixed** (fewer than 20 items) - Users can scan options without search
* **Filtering content** - Multiple criteria can be active simultaneously
* **Assigning attributes** - Labels, permissions, or features where multiple values make sense
* **Related choices** - Options that logically work together (such as selecting multiple team members)

Avoid this pattern when:

* **Only single selection is needed** - Use the [Select pattern](guide/aria/select) for simpler single-choice dropdowns
* **The list has more than 20 items with search needed** - Use the [Autocomplete pattern](guide/aria/autocomplete) with multiselect capability
* **Most or all options will be selected** - A checklist pattern provides better visibility
* **Choices are independent binary options** - Individual checkboxes communicate the choices more clearly

## [Features](/guide/aria/multiselect#features)

The multiselect pattern combines [Combobox](guide/aria/combobox) and [Listbox](guide/aria/listbox) directives to provide a fully accessible dropdown with:

* **Keyboard Navigation** - Navigate options with arrow keys, toggle with Space, close with Escape
* **Screen Reader Support** - Built-in ARIA attributes including aria-multiselectable
* **Selection Count Display** - Shows compact "Item + 2 more" pattern for multiple selections
* **Signal-Based Reactivity** - Reactive state management using Angular signals
* **Smart Positioning** - CDK Overlay handles viewport edges and scrolling
* **Persistent Selection** - Selected options remain visible with checkmarks after selection

## [Examples](/guide/aria/multiselect#examples)

### [Basic multiselect](/guide/aria/multiselect#basic-multiselect)

Users need to select multiple items from a list of options. A readonly combobox paired with a multi-enabled listbox provides familiar multiselect functionality with full accessibility support.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 2.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 1.5rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="basic-material"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 3rem;
  padding: 0 2.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1.5rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 13rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="basic-retro"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 4.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 22rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

The `multi` attribute on [`ngListbox`](/api/aria/listbox/Listbox) enables multiple selection. Press Space to toggle options, and the popup remains open for additional selections. The display shows the first selected item plus a count of remaining selections.

### [Multiselect with custom display](/guide/aria/multiselect#multiselect-with-custom-display)

Options often need visual indicators like icons or colors to help users identify choices. Custom templates within options allow rich formatting while the display value shows a compact summary.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="icons-basic"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The icon that is displayed in the combobox. */
  readonly displayIcon = computed(() => {
    const values = this.selectedValues();
    const label = this.labels.find((label) => label.value === values[0]);
    return label ? label.icon : '';
  });

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 3.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="icons-material"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The icon that is displayed in the combobox. */
  readonly displayIcon = computed(() => {
    const values = this.selectedValues();
    const label = this.labels.find((label) => label.value === values[0]);
    return label ? label.icon : '';
  });

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 3rem;
  padding: 0 3.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 13rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="icons-retro"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The icon that is displayed in the combobox. */
  readonly displayIcon = computed(() => {
    const values = this.selectedValues();
    const label = this.labels.find((label) => label.value === values[0]);
    return label ? label.icon : '';
  });

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select a label';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} + ${values.length - 1} more`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 4.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 22rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

Each option displays an icon alongside its label. The display value updates to show the first selection's icon and text, followed by a count of additional selections. Selected options show a checkmark for clear visual feedback.

### [Controlled selection](/guide/aria/multiselect#controlled-selection)

Forms sometimes need to limit the number of selections or validate user choices. Programmatic control over selection enables these constraints while maintaining accessibility.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="limited-basic"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select 2 labels';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} & ${values[1]}`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', disabled: computed(() => this.isOptionDisabled('Important'))},
    {value: 'Starred', disabled: computed(() => this.isOptionDisabled('Starred'))},
    {value: 'Work', disabled: computed(() => this.isOptionDisabled('Work'))},
    {value: 'Personal', disabled: computed(() => this.isOptionDisabled('Personal'))},
    {value: 'To Do', disabled: computed(() => this.isOptionDisabled('To Do'))},
    {value: 'Later', disabled: computed(() => this.isOptionDisabled('Later'))},
    {value: 'Read', disabled: computed(() => this.isOptionDisabled('Read'))},
    {value: 'Travel', disabled: computed(() => this.isOptionDisabled('Travel'))},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  isOptionDisabled(value: string) {
    const values = this.selectedValues();
    if (!values || values.length < 2) {
      return false;
    }
    return !values.includes(value);
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value" [disabled]="label.disabled()">
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 2.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 1.5rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

[ngOption][aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

[ngOption][aria-disabled='true']:hover {
  background-color: transparent;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="limited-material"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select 2 labels';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} & ${values[1]}`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', disabled: computed(() => this.isOptionDisabled('Important'))},
    {value: 'Starred', disabled: computed(() => this.isOptionDisabled('Starred'))},
    {value: 'Work', disabled: computed(() => this.isOptionDisabled('Work'))},
    {value: 'Personal', disabled: computed(() => this.isOptionDisabled('Personal'))},
    {value: 'To Do', disabled: computed(() => this.isOptionDisabled('To Do'))},
    {value: 'Later', disabled: computed(() => this.isOptionDisabled('Later'))},
    {value: 'Read', disabled: computed(() => this.isOptionDisabled('Read'))},
    {value: 'Travel', disabled: computed(() => this.isOptionDisabled('Travel'))},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  isOptionDisabled(value: string) {
    const values = this.selectedValues();
    if (!values || values.length < 2) {
      return false;
    }
    return !values.includes(value);
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value" [disabled]="label.disabled()">
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  /* Modernized additions for div-trigger Select: */
  height: 3rem;
  padding: 0 2.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1.5rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 13rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

[ngOption][aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

[ngOption][aria-disabled='true']:hover {
  background-color: transparent;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild, effect} from '@angular/core';

@Component({
  selector: 'app-root:not([theme="limited-retro"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  /** The combobox listbox popup. */
  readonly listbox = viewChild(Listbox);

  /** The options available in the listbox. */
  readonly selectedValues = signal<string[]>([]);

  /** The string that is displayed in the combobox. */
  readonly displayValue = computed(() => {
    const values = this.selectedValues();
    if (values.length === 0) {
      return 'Select 2 labels';
    }
    if (values.length === 1) {
      return values[0];
    }
    return `${values[0]} & ${values[1]}`;
  });

  /** The labels that are available for selection. */
  readonly labels = [
    {value: 'Important', disabled: computed(() => this.isOptionDisabled('Important'))},
    {value: 'Starred', disabled: computed(() => this.isOptionDisabled('Starred'))},
    {value: 'Work', disabled: computed(() => this.isOptionDisabled('Work'))},
    {value: 'Personal', disabled: computed(() => this.isOptionDisabled('Personal'))},
    {value: 'To Do', disabled: computed(() => this.isOptionDisabled('To Do'))},
    {value: 'Later', disabled: computed(() => this.isOptionDisabled('Later'))},
    {value: 'Read', disabled: computed(() => this.isOptionDisabled('Read'))},
    {value: 'Travel', disabled: computed(() => this.isOptionDisabled('Travel'))},
  ];

  /** Whether the popup is expanded. */
  readonly popupExpanded = signal(false);

  constructor() {
    // Scrolls to the active item when the active option changes.
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  isOptionDisabled(value: string) {
    const values = this.selectedValues();
    if (!values || values.length < 2) {
      return false;
    }
    return !values.includes(value);
  }
}
```

### app.html

```
<div
  #combobox="ngCombobox"
  ngCombobox
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        [multi]="true"
        ngComboboxWidget
        focusMode="activedescendant"
        [tabindex]="-1"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value" [disabled]="label.disabled()">
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  /* Modernized additions for div-trigger Select: */
  height: 2.5rem;
  padding: 0 4.5rem;
  cursor: pointer;
  box-sizing: border-box;
  width: 22rem;
  user-select: none;
  -webkit-user-select: none;
  outline: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

.example-popup-container.closing {
  animation: smoothPopupClose 150ms ease-in forwards;
}

@keyframes smoothPopupClose {
  0% {
    max-height: 11rem;
    opacity: 1;
  }
  100% {
    max-height: 0;
    opacity: 0;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
  outline: none;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

[ngOption][aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

[ngOption][aria-disabled='true']:hover {
  background-color: transparent;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

This example limits selections to two items. When the limit is reached, unselected options are disabled to prevent further selections, and the combobox display updates to reflect the choices.

## [Testing](/guide/aria/multiselect#testing)

The multiselect pattern can be tested using a combination of `ComboboxHarness` and `ListboxHarness` from `@angular/aria/combobox/testing` and `@angular/aria/listbox/testing`. Here is an example of how to use the harnesses to test a multiselect component:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComboboxHarness} from '@angular/aria/combobox/testing';
import {ListboxHarness} from '@angular/aria/listbox/testing';
import {MyMultiselectComponent} from './my-multiselect'; // Your component

describe('MyMultiselectComponent', () => {
  let fixture: ComponentFixture<MyMultiselectComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyMultiselectComponent],
    });

    fixture = TestBed.createComponent(MyMultiselectComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should allow selecting multiple options', async () => {
    const select = await loader.getHarness(ComboboxHarness);

    // Open the dropdown
    await select.open();

    // Get the listbox harness from the popup
    const listbox = await select.getPopupWidget(ListboxHarness);
    expect(await listbox.isMulti()).toBe(true);

    const options = await listbox.getOptions();

    // Select first and second options
    await options[0].click();
    await options[1].click();

    // Verify both options are selected
    expect(await options[0].isSelected()).toBe(true);
    expect(await options[1].isSelected()).toBe(true);

    // Close the dropdown
    await select.close();

    // Verify value is updated (e.g., comma separated list or count)
    expect(await (await select.host()).text()).toContain('Option 1, Option 2');
  });
});
```

## [API reference](/guide/aria/multiselect#api-reference)

For detailed API documentation, inspect the following API references:

* [`Combobox`](/api/aria/combobox/Combobox)
* [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup)
* [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget)
* [`Listbox`](/api/aria/listbox/Listbox)
* [`Option`](/api/aria/listbox/Option)

Copy link to example

----
url: https://angular.dev/api/core/InputOptions
----

```
    
      interface InputOptions<T, TransformT> {}
    
    
```

Optional public name for the input. By default, the class field name is used.

Optional transform that runs whenever a new value is bound. Can be used to transform the input value before the input is updated.

The transform function can widen the type of the input. For example, consider an input for `disabled`. In practice, as the component author, you want to only deal with a boolean, but users may want to bind a string if they just use the attribute form to bind to the input via `<my-dir input>`. A transform can then handle such string values and convert them to `boolean`. See: [`booleanAttribute`](/api/core/booleanAttribute).

A debug name for the input signal. Used in Angular DevTools to identify the signal.

Jump to details

----
url: https://angular.dev/api/common/getLocalePluralCase
----

Angular recommends relying on the `Intl` API for i18n. Use `Intl.PluralRules` instead

## [API](#api)

```
    
      const getLocalePluralCase: (locale: string) => (value: number) => Plural;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpContext
----

## [API](/api/common/http/HttpContext#api)

```
    
      class HttpContext {}
    
    
```

Store a value in the context. If a value is already present it will be overwritten.

@paramtoken`HttpContextToken<T>`

The reference to an instance of [`HttpContextToken`](/api/common/http/HttpContextToken).

@paramvalue`T`

The value to store.

@returns`HttpContext`

A reference to itself for easy chaining.

Retrieve the value associated with the given token.

@paramtoken`HttpContextToken<T>`

The reference to an instance of [`HttpContextToken`](/api/common/http/HttpContextToken).

@returns`T`

The stored value or default if one is defined.

Delete the value associated with the given token.

@paramtoken`HttpContextToken<unknown>`

The reference to an instance of [`HttpContextToken`](/api/common/http/HttpContextToken).

@returns`HttpContext`

A reference to itself for easy chaining.

Checks for existence of a given token.

@paramtoken`HttpContextToken<unknown>`

The reference to an instance of [`HttpContextToken`](/api/common/http/HttpContextToken).

@returns`boolean`

True if the token exists, false otherwise.

@returns`IterableIterator<HttpContextToken<unknown>>`

a list of tokens currently stored in the context.

## [Description](/api/common/http/HttpContext#description)

Http context stores arbitrary user defined values and ensures type safety without actually knowing the types. It is backed by a `Map` and guarantees that keys do not clash.

This context is mutable and is shared between cloned requests unless explicitly specified.

## [Usage Notes](/api/common/http/HttpContext#usage-notes)

### [Usage Example](/api/common/http/HttpContext#usage-example)

```
// inside cache.interceptors.ts
export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);

export class CacheInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
    if (req.context.get(IS_CACHE_ENABLED) === true) {
      return ...;
    }
    return delegate.handle(req);
  }
}

// inside a service

this.httpClient.get('/api/weather', {
  context: new HttpContext().set(IS_CACHE_ENABLED, true)
}).subscribe(...);
```

Jump to details

----
url: https://angular.dev/api/elements/NgElementConstructor
----

```
    
      interface NgElementConstructor<P> {}
    
    
```

An array of observed attribute names for the custom element, derived by transforming input property names from the source component.

Jump to details

----
url: https://angular.dev/tools/devtools/profiler
----

The **Profiler** tab lets you visualize the execution of Angular's change detection. This is useful for determining when and how change detection impacts your application's performance.

The Profiler tab lets you start profiling the current application or import an existing profile from a previous run. To start profiling your application, hover over the circle in the top-left corner within the **Profiler** tab and click **Start recording**.

During profiling, Angular DevTools captures execution events, such as change detection and lifecycle hook execution. Interact with your application to trigger change detection and generate data Angular DevTools can use. To finish recording, click the circle again to **Stop recording**.

You can also import an existing recording. Read more about this feature in the [Import recording](tools/devtools/profiler#import-and-export-recordings) section.

## [Understand your application's execution](#understand-your-applications-execution)

After recording or importing a profile, Angular DevTools displays a visualization of change detection cycles.

Each bar in the sequence represents a change detection cycle in your app. The taller a bar is, the longer the application spent running change detection in this cycle. When you select a bar, DevTools displays useful information about it including:

* A bar chart with all the components and directives that it captured during this cycle
* How much time Angular spent running change detection in this cycle.
* An estimated frame rate as experienced by the user (if below 60fps)

## [Understand component execution](#understand-component-execution)

The bar chart displayed after clicking on a change detection cycle displays a detailed view about how much time your application spent running change detection in that particular component or directive.

This example shows the total time spent by the [`NgForOf`](/api/common/NgForOf) directive and which method was called on it.

## [Hierarchical views](#hierarchical-views)

You can also visualize the change detection execution in a flame graph-like view.

Each tile in the graph represents an element on the screen at a specific position in the render tree. For example, consider a change detection cycle where a `LoggedOutUserComponent` is removed and in its place Angular rendered a `LoggedInUserComponent`. In this scenario both components will be displayed in the same tile.

The x-axis represents the full time it took to render this change detection cycle. The y-axis represents the element hierarchy. Running change detection for an element requires rendering its directives and child components. Together, this graph visualizes which components are taking the longest time to render and where that time is going.

Each tile is colored depending on how much time Angular spent there. Angular DevTools determines the intensity of the color by the time spent relative to the tile where rendering took the most time.

When you click on a certain tile, you'll see details about it in the panel on the right. Double-clicking the tile zooms it in so you can more easily view its nested children.

## [Debug change detection and `OnPush` components](#debug-change-detection-and-onpush-components)

Normally, the graph visualizes the time it takes to *render* an application, for any given change detection frame. However some components such as `OnPush` components will only re-render if their input properties change. It can be useful to visualize the flame graph without these components for particular frames.

To visualize only the components in a change detection frame that went through the change detection process, select the **Change detection** checkbox at the top, above the flame graph.

This view highlights all the components that went through change detection and displays those that did not in gray, such as `OnPush` components that did not re-render.

## [Import and export recordings](#import-and-export-recordings)

Click the **Save Profile** button at the top-right of a recorded profiling session to export it as a JSON file and save it to the disk. Later, import the file in the initial view of the profiler by clicking the **Choose file** input.

----
url: https://angular.dev/api/forms/signals/schema
----

```
function schema<TValue>(fn: SchemaFn<TValue>): Schema<TValue>;
```

@paramfn`SchemaFn<TValue>`

A **non-reactive** function that sets up reactive logic rules for the form.

@returns`Schema<TValue>`

A schema object that implements the given logic.

Jump to details

----
url: https://angular.dev/errors/NG0506
----

This warning only appears in the browser during the hydration process when it's enabled on the client but the application remains unstable for an extended period of time (over 10 seconds).

Typically that happens when there are some pending microtasks or macrotasks on a page.

Angular Hydration relies on a signal from [`ApplicationRef.isStable`](/api/core/ApplicationRef#isStable) when it becomes stable inside an application:

* during the server-side rendering (SSR) to start the serialization process
* in a browser this signal is used to start the post-hydration cleanup to remove DOM nodes that remained unclaimed

This warning is displayed when [`ApplicationRef.isStable`](/api/core/ApplicationRef#isStable) does not emit `true` within 10 seconds. If this behavior is intentional and your application stabilizes later, you could choose to ignore this warning.

## [Applications that use zone.js](#applications-that-use-zonejs)

Applications that use zone.js may have various factors contributing to delays in stability. These may include pending HTTP requests, timers (`setInterval`, `setTimeout`) or some logic that continuously invokes `requestAnimationFrame`.

### [Macrotasks](#macrotasks)

Macrotasks include functions like `setInterval`, `setTimeout`, `requestAnimationFrame`, etc. If one of these functions is called during the initialization phase of the application or in bootstrapped components, it may delay the moment when the application becomes stable.

```
@Component({
  selector: 'app',
  template: ``,
})
class Simple {
  constructor() {
    setInterval(() => { ... }, 1000)

    // or

    setTimeout(() => { ... }, 10_000)
  }
}
```

If these functions must be called during the initialization phase, invoking them outside the Angular zone resolves the problem:

```
class Simple {
  constructor() {
    const ngZone = inject(NgZone);

    ngZone.runOutsideAngular(() => {
      setInterval(() => {}, 1000);
    });
  }
}
```

### [Third-party libraries](#third-party-libraries)

Some third-party libraries can also produce long-running asynchronous tasks, which may delay application stability. The recommendation is to invoke relevant library code outside of the zone as described above.

### [Running code after an application becomes stable](#running-code-after-an-application-becomes-stable)

You can run a code that sets up asynchronous tasks once an application becomes stable:

```
class Simple {
  constructor() {
    const applicationRef = inject(ApplicationRef);

    applicationRef.isStable.pipe( first((isStable) => isStable) ).subscribe(() => {
      // Note that we don't need to use `runOutsideAngular` because `isStable`
      // emits events outside of the Angular zone when it's truthy (falsy values
      // are emitted inside the Angular zone).
      setTimeout(() => { ... });
    });
  }
}
```

## [Zoneless applications](#zoneless-applications)

In zoneless scenarios, stability might be delayed by an application code inside of an [`effect`](/api/core/effect) running in an infinite loop (potentially because signals used in effect functions keep changing) or a pending HTTP request.

Developers may also explicitly contribute to indicating the application's stability by using the [`PendingTasks`](/api/core/PendingTasks) service. If you use the mentioned APIs in your application, make sure you invoke a function to mark the task as completed.

----
url: https://angular.dev/errors/NG0200
----

[](https://www.youtube.com/watch?v=CpLOm4o_FzM\&autoplay=1)

A cyclic dependency exists when a [dependency of a service](guide/di/hierarchical-dependency-injection) directly or indirectly depends on the service itself. For example, if `UserService` depends on `EmployeeService`, which also depends on `UserService`. Angular will have to instantiate `EmployeeService` to create `UserService`, which depends on `UserService`, itself.

## [Debugging the error](#debugging-the-error)

Use the call stack to determine where the cyclical dependency exists. You will be able to see if any child dependencies rely on the original file by [mapping out](guide/di/di-in-action) the component, module, or service's dependencies, and identifying the loop causing the problem.

Break this loop (or circle) of dependency to resolve this error. This most commonly means removing or refactoring the dependencies to not be reliant on one another.

----
url: https://angular.dev/api/core/FactoryProvider
----

```
    
      interface FactoryProvider extends FactorySansProvider {}
    
    
```

An injection token. (Typically an instance of [`Type`](/api/core/Type) or [`InjectionToken`](/api/core/InjectionToken), but can be `any`).

When true, injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

A function to invoke to create a value for this `token`. The function is invoked with resolved values of `token`s in the `deps` field.

A list of `token`s to be resolved by the injector. The list of values is then used as arguments to the `useFactory` function.

## [Usage Notes](#usage-notes)

```
const Location = new InjectionToken('location');
        const Hash = new InjectionToken('hash');

        const injector = Injector.create({
          providers: [
            {provide: Location, useValue: 'https://angular.io/#someLocation'},
            {
              provide: Hash,
              useFactory: (location: string) => location.split('#')[1],
              deps: [Location],
            },
          ],
        });

        expect(injector.get(Hash)).toEqual('someLocation');
```

Dependencies can also be marked as optional:

```
const Location = new InjectionToken('location');
        const Hash = new InjectionToken('hash');

        const injector = Injector.create({
          providers: [
            {
              provide: Hash,
              useFactory: (location: string) => `Hash for: ${location}`,
              // use a nested array to define metadata for dependencies.
              deps: [[new Optional(), Location]],
            },
          ],
        });

        expect(injector.get(Hash)).toEqual('Hash for: null');
```

### [Multi-value example](#multi-value-example)

```
const locale = new InjectionToken<string[]>('locale');
        const injector = Injector.create({
          providers: [
            {provide: locale, multi: true, useValue: 'en'},
            {provide: locale, multi: true, useValue: 'sk'},
          ],
        });

        const locales: string[] = injector.get(locale);
        expect(locales).toEqual(['en', 'sk']);
```

Jump to details

----
url: https://angular.dev/api/common/getLocaleDateFormat
----

```
function getLocaleDateFormat(locale: string, width: FormatWidth): string;
```

----
url: https://angular.dev/api/core/getPlatform
----

```
function getPlatform(): PlatformRef | null;
```

@returns`PlatformRef | null`

Jump to details

----
url: https://angular.dev/api/core/resourceFromSnapshots
----

```
function resourceFromSnapshots<T>(
  source: () => ResourceSnapshot<T>,
): Resource<T>;
```

@paramsource`() => ResourceSnapshot<T>`

@returns`Resource<T>`

Jump to details

----
url: https://angular.dev/tutorials/signals
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/1-creating-your-first-signal)

[](/tutorials/signals/1-creating-your-first-signal)

Welcome! Let's explore signals.

This interactive tutorial will teach you the fundamentals of Angular signals and how to use them to build reactive applications.

## [How to use this tutorial](#how-to-use-this-tutorial)

This tutorial assumes you understand Angular's core concepts. If you're new to Angular, read our [essentials guide](/essentials).

Each step represents a concept in Angular signals. You can do one, or all of them.

If you get stuck, click "Reveal answer" at the top.

Alright, let's [get started](/tutorials/signals/1-creating-your-first-signal)!

----
url: https://angular.dev/tools/cli/schematics
----

A schematic is a template-based code generator that supports complex logic. It is a set of instructions for transforming a software project by generating or modifying code. Schematics are packaged into collections and installed with npm.

The schematic collection can be a powerful tool for creating, modifying, and maintaining any software project, but is particularly useful for customizing Angular projects to suit the particular needs of your own organization. You might use schematics, for example, to generate commonly-used UI patterns or specific components, using predefined templates or layouts. Use schematics to enforce architectural rules and conventions, making your projects consistent and interoperative.

## [Schematics for the Angular CLI](#schematics-for-the-angular-cli)

Schematics are part of the Angular ecosystem. The Angular CLI uses schematics to apply transforms to a web-app project. You can modify these schematics, and define new ones to do things like update your code to fix breaking changes in a dependency, for example, or to add a new configuration option or framework to an existing project.

Schematics that are included in the `@schematics/angular` collection are run by default by the commands `ng generate` and `ng add`. The package contains named schematics that configure the options that are available to the CLI for `ng generate` sub-commands, such as `ng generate component` and `ng generate service`. The sub-commands for `ng generate` are shorthand for the corresponding schematic. To specify and generate a particular schematic, or a collection of schematics, using the long form:

```
ng generate my-schematic-collection:my-schematic-name
```

### [Configuring CLI schematics](#configuring-cli-schematics)

A JSON schema associated with a schematic tells the Angular CLI what options are available to commands and sub-commands, and determines the defaults. These defaults can be overridden by providing a different value for an option on the command line. See [Workspace Configuration](reference/configs/workspace-config) for information about how to change the generation option defaults for your workspace.

The JSON schemas for the default schematics used by the CLI to generate projects and parts of projects are collected in the package [`@schematics/angular`](https://github.com/angular/angular-cli/tree/main/packages/schematics/angular). The schema describes the options available to the CLI for each of the `ng generate` sub-commands, as shown in the `--help` output.

## [Developing schematics for libraries](#developing-schematics-for-libraries)

As a library developer, you can create your own collections of custom schematics to integrate your library with the Angular CLI.

* An *add schematic* lets developers install your library in an Angular workspace using `ng add`
* *Generation schematics* can tell the `ng generate` sub-commands how to modify projects, add configurations and scripts, and scaffold artifacts that are defined in your library
* An *update schematic* can tell the `ng update` command how to update your library's dependencies and adjust for breaking changes when you release a new version

For more details of what these look like and how to create them, see:

### [Add schematics](#add-schematics)

An *add schematic* is typically supplied with a library, so that the library can be added to an existing project with `ng add`. The `add` command uses your package manager to download new dependencies, and invokes an installation script that is implemented as a schematic.

For example, the [`@angular/material`](https://material.angular.dev/guide/schematics) schematic tells the `add` command to install and set up Angular Material and theming, and register new starter components that can be created with `ng generate`. Look at this one as an example and model for your own add schematic.

Partner and third party libraries also support the Angular CLI with add schematics. For example, `@ng-bootstrap/schematics` adds [ng-bootstrap](https://ng-bootstrap.github.io) to an app, and `@clr/angular` installs and sets up [Clarity from VMWare](https://clarity.design/documentation/get-started).

An *add schematic* can also update a project with configuration changes, add additional dependencies (such as polyfills), or scaffold package-specific initialization code. For example, the `@angular/pwa` schematic turns your application into a PWA by adding an application manifest and service worker.

### [Generation schematics](#generation-schematics)

Generation schematics are instructions for the `ng generate` command. The documented sub-commands use the default Angular generation schematics, but you can specify a different schematic (in place of a sub-command) to generate an artifact defined in your library.

Angular Material, for example, supplies generation schematics for the UI components that it defines. The following command uses one of these schematics to render an Angular Material `<mat-table>` that is pre-configured with a datasource for sorting and pagination.

```
ng generate @angular/material:table <component-name>
```

### [Update schematics](#update-schematics)

The `ng update` command can be used to update your workspace's library dependencies. If you supply no options or use the help option, the command examines your workspace and suggests libraries to update.

```
ng update
We analyzed your package.json, there are some packages to update:

    Name                                      Version                     Command to update
    --------------------------------------------------------------------------------
    @angular/cdk                       7.2.2 -> 7.3.1           ng update @angular/cdk
    @angular/cli                       7.2.3 -> 7.3.0           ng update @angular/cli
    @angular/core                      7.2.2 -> 7.2.3           ng update @angular/core
    @angular/material                  7.2.2 -> 7.3.1           ng update @angular/material
    rxjs                                      6.3.3 -> 6.4.0           ng update rxjs
```

If you pass the command a set of libraries to update, it updates those libraries, their peer dependencies, and the peer dependencies that depend on them.

**HELPFUL:** If there are inconsistencies (for example, if peer dependencies cannot be matched by a simple [semver](https://semver.org) range), the command generates an error and does not change anything in the workspace.

We recommend that you do not force an update of all dependencies by default. Try updating specific dependencies first.

For more about how the `ng update` command works, see [Update Command](https://github.com/angular/angular-cli/blob/main/docs/specifications/update.md).

If you create a new version of your library that introduces potential breaking changes, you can provide an *update schematic* to enable the `ng update` command to automatically resolve any such changes in the project being updated.

For example, suppose you want to update the Angular Material library.

```
ng update @angular/material
```

This command updates both `@angular/material` and its dependency `@angular/cdk` in your workspace's `package.json`. If either package contains an update schematic that covers migration from the existing version to a new version, the command runs that schematic on your workspace.

----
url: https://angular.dev/api/core/GetTestability
----

```
    
      interface GetTestability {}
    
    
```

@paramregistry`TestabilityRegistry`

@returns`void`

@paramregistry`TestabilityRegistry`

@paramelem`any`

@paramfindInAncestors`boolean`

@returns`Testability | null`

Jump to details

----
url: https://angular.dev/api/core/TestabilityRegistry
----

```
    
      class TestabilityRegistry {}
    
    
```

Registers an application with a testability hook so that it can be tracked

@paramtoken`any`

token of application, root element

@paramtestability`Testability`

Testability hook

@returns`void`

Unregisters an application.

@paramtoken`any`

token of application, root element

@returns`void`

Unregisters all applications

@returns`void`

Get a testability hook associated with the application

@paramelem`any`

root element

@returns`Testability | null`

Get all registered testabilities

@returns`Testability[]`

Get all registered applications(root elements)

@returns`any[]`

Find testability of a node in the Tree

@paramelem`Node`

node

@paramfindInAncestors`boolean`

whether finding testability in ancestors if testability was not found in current node

@returns`Testability | null`

Jump to details

----
url: https://angular.dev/api/core/globals/getInjector
----

```
function getInjector(elementOrDir: {} | Element): Injector;
```

@paramelementOrDir`{} | Element`

DOM element, component or directive instance for which to retrieve the injector.

@returns`Injector`

Injector associated with the element, component or directive instance.

Jump to details

----
url: https://angular.dev/api/upgrade/static/UpgradeModule
----

```
    
      class UpgradeModule {}
    
    
```

@paraminjector`Injector`

The root [`Injector`](/api/core/Injector) for the upgrade application.

@paramngZone`NgZone`

The bootstrap zone for the upgrade application

@paramplatformRef`PlatformRef`

The owning [`NgModuleRef`](/api/core/NgModuleRef)s [`PlatformRef`](/api/core/PlatformRef) instance. This is used to tie the lifecycle of the bootstrapped AngularJS apps to that of the Angular [`PlatformRef`](/api/core/PlatformRef).

@returns`UpgradeModule`

The AngularJS `$injector` for the upgrade application.

The Angular Injector \*

The bootstrap zone for the upgrade application

Bootstrap an AngularJS application from this NgModule

@paramelement`Element`

the element on which to bootstrap the AngularJS application

@parammodules`string[]`

the AngularJS modules to bootstrap for this application

@paramconfig`any`

optional extra AngularJS bootstrap configuration

@returns`any`

## [Description](#description)

An [`NgModule`](/api/core/NgModule), which you import to provide AngularJS core services, and has an instance method used to bootstrap the hybrid upgrade application.

*Part of the [upgrade/static](api?query=upgrade/static) library for hybrid upgrade apps that support AOT compilation*

The `upgrade/static` package contains helpers that allow AngularJS and Angular components to be used together inside a hybrid upgrade application, which supports AOT compilation.

Specifically, the classes and functions in the `upgrade/static` module allow the following:

1. Creation of an Angular directive that wraps and exposes an AngularJS component so that it can be used in an Angular template. See [`UpgradeComponent`](/api/upgrade/static/UpgradeComponent).
2. Creation of an AngularJS directive that wraps and exposes an Angular component so that it can be used in an AngularJS template. See [`downgradeComponent`](/api/upgrade/static/downgradeComponent).
3. Creation of an Angular root injector provider that wraps and exposes an AngularJS service so that it can be injected into an Angular context. See [`Upgrading an AngularJS service`](/api/upgrade/static/UpgradeModule#upgrading-an-angularjs-service) below.
4. Creation of an AngularJS service that wraps and exposes an Angular injectable so that it can be injected into an AngularJS context. See [`downgradeInjectable`](/api/upgrade/static/downgradeInjectable).
5. Bootstrapping of a hybrid Angular application which contains both of the frameworks coexisting in a single application.

## [Usage Notes](#usage-notes)

```
import {UpgradeModule} from '@angular/upgrade/static';
```

See also the [`examples`](/api/upgrade/static/UpgradeModule#examples) below.

### [Mental Model](#mental-model)

When reasoning about how a hybrid application works it is useful to have a mental model which describes what is happening and explains what is happening at the lowest level.

1. There are two independent frameworks running in a single application, each framework treats the other as a black box.

2. Each DOM element on the page is owned exactly by one framework. Whichever framework instantiated the element is the owner. Each framework only updates/interacts with its own DOM elements and ignores others.

3. AngularJS directives always execute inside the AngularJS framework codebase regardless of where they are instantiated.

4. Angular components always execute inside the Angular framework codebase regardless of where they are instantiated.

5. An AngularJS component can be "upgraded"" to an Angular component. This is achieved by defining an Angular directive, which bootstraps the AngularJS component at its location in the DOM. See [`UpgradeComponent`](/api/upgrade/static/UpgradeComponent).

6. An Angular component can be "downgraded" to an AngularJS component. This is achieved by defining an AngularJS directive, which bootstraps the Angular component at its location in the DOM. See [`downgradeComponent`](/api/upgrade/static/downgradeComponent).

7. Whenever an "upgraded"/"downgraded" component is instantiated the host element is owned by the framework doing the instantiation. The other framework then instantiates and owns the view for that component.

   1. This implies that the component bindings will always follow the semantics of the instantiation framework.
   2. The DOM attributes are parsed by the framework that owns the current template. So attributes in AngularJS templates must use kebab-case, while AngularJS templates must use camelCase.
   3. However the template binding syntax will always use the Angular style, e.g. square brackets (`[...]`) for property binding.

8. Angular is bootstrapped first; AngularJS is bootstrapped second. AngularJS always owns the root component of the application.

9. The new application is running in an Angular zone, and therefore it no longer needs calls to `$apply()`.

### [The ``](#the-upgrademodule-class)`UpgradeModule` class

This class is an [`NgModule`](/api/core/NgModule), which you import to provide AngularJS core services, and has an instance method used to bootstrap the hybrid upgrade application.

* Core AngularJS services\
  Importing this [`NgModule`](/api/core/NgModule) will add providers for the core [AngularJS services](https://docs.angularjs.org/api/ng/service) to the root injector.

* Bootstrap\
  The runtime instance of this class contains a [`bootstrap()`](/api/upgrade/static/UpgradeModule#bootstrap) method, which you use to bootstrap the top level AngularJS module onto an element in the DOM for the hybrid upgrade app.

  It also contains properties to access the [`root injector`](/api/upgrade/static/UpgradeModule#injector), the bootstrap [`NgZone`](/api/core/NgZone) and the [AngularJS $injector](https://docs.angularjs.org/api/auto/service/$injector).

### [Examples](#examples)

Import the [`UpgradeModule`](/api/upgrade/static/UpgradeModule) into your top level Angular [`NgModule`](/api/core/NgModule).

```
// This NgModule represents the Angular pieces of the application
@NgModule({
  declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
  providers: [
    HeroesService,
    // Register an Angular provider whose value is the "upgraded" AngularJS service
    {provide: TextFormatter, useFactory: (i: any) => i.get('textFormatter'), deps: ['$injector']},
  ],
  // We must import `UpgradeModule` to get access to the AngularJS core services
  imports: [BrowserModule, UpgradeModule],
})
export class Ng2AppModule {
}
```

Then inject [`UpgradeModule`](/api/upgrade/static/UpgradeModule) into your Angular [`NgModule`](/api/core/NgModule) and use it to bootstrap the top level [AngularJS module](https://docs.angularjs.org/api/ng/type/angular.Module) in the `ngDoBootstrap()` method.

```
export class Ng2AppModule {
  constructor(private upgrade: UpgradeModule) {}

  ngDoBootstrap() {
    // We bootstrap the AngularJS app.
    this.upgrade.bootstrap(document.body, [ng1AppModule.name]);
  }
}
```

Finally, kick off the whole process, by bootstrapping your top level Angular [`NgModule`](/api/core/NgModule).

```
// We bootstrap the Angular module as we would do in a normal Angular app.
// (We are using the dynamic browser platform as this example has not been compiled AOT.)
platformBrowser().bootstrapModule(Ng2AppModule);
```

### [Upgrading an AngularJS service](#upgrading-an-angularjs-service)

There is no specific API for upgrading an AngularJS service. Instead you should just follow the following recipe:

Let's say you have an AngularJS service:

```
export class TextFormatter {
  titleCase(value: string) {
    return value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase());
  }
}

// This AngularJS service will be "upgraded" to be used in Angular
ng1AppModule.service('textFormatter', [TextFormatter]);
```

Then you should define an Angular provider to be included in your [`NgModule`](/api/core/NgModule) `providers` property.

```
// Register an Angular provider whose value is the "upgraded" AngularJS service
    {provide: TextFormatter, useFactory: (i: any) => i.get('textFormatter'), deps: ['$injector']},
```

Then you can use the "upgraded" AngularJS service by injecting it into an Angular component or service.

```
constructor(textFormatter: TextFormatter) {
    // Change all the hero names to title case, using the "upgraded" AngularJS service
    this.heroes.forEach((hero: Hero) => (hero.name = textFormatter.titleCase(hero.name)));
  }
```

Jump to details

----
url: https://angular.dev/cli/generate/guard
----

```
ng generate guard [name] [options]
```

```
ng generate g [name] [options]
```

Creates a new route guard in your project. Route guards are used to control access to parts of your application by checking certain conditions before a route is activated. This schematic generates a new guard with the specified name, type, and options.

## [Arguments](#arguments)

`name`

The name for the new route guard. This will be used to create the guard's class and spec files (e.g., `my-guard.guard.ts` and `my-guard.guard.spec.ts`).

Value Type`string`

## [Options](#options)

`flat`

Creates the new guard files at the top level of the current project. If set to false, a new folder with the guard's name will be created to contain the files.

Value Type`boolean`Default`true`

`functional`

Generate the guard as a function instead of a class. Functional guards can be simpler for basic scenarios.

Value Type`boolean`Default`true`

`implements`

Alias`guardType`

Specifies the type(s) of guard to create. You can choose one or more of the following: `CanActivate` (controls access to a route), `CanActivateChild` (controls access to child routes), `CanDeactivate` (asks for confirmation before leaving a route), `CanMatch` (determines if a route can be matched).

Value Type`array`Allowed Values`CanActivate`, `CanActivateChild`, `CanDeactivate`, `CanMatch`Default`CanActivate`

`project`

The name of the project where the guard should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`skip-tests`

Skip the generation of a unit test file `spec.ts` for the new guard.

Value Type`boolean`Default`false`

`type-separator`

The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.guard.ts`.

Value Type`string`Allowed Values`-`, `.`Default`-`

Jump to details

----
url: https://angular.dev/tutorials/signal-forms
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/1-set-up-form-model)

[](/tutorials/signal-forms/1-set-up-form-model)

Welcome! Let's build forms with signals.

This interactive tutorial will teach you how to build reactive forms using Angular's Signal Forms API.

## [How to use this tutorial](#how-to-use-this-tutorial)

This tutorial assumes you understand Angular's core concepts and have basic familiarity with signals. If you're new to Angular, read our [essentials guide](/essentials). If you're new to signals, complete the [signals tutorial](/tutorials/signals) first.

Each step represents a concept in Signal Forms. You'll build a complete login form from scratch, learning the fundamentals step by step.

**Your learning path:**

1. Set up the form model with TypeScript and signals
2. Connect the form to your template
3. Add validation rules
4. Display validation errors to users
5. Handle form submission
6. Explore advanced topics and next steps

If you get stuck, click "Reveal answer" at the top.

Alright, let's [get started](/tutorials/signal-forms/1-set-up-form-model)!

----
url: https://angular.dev/api/core/testing/TestDirectiveOptions
----

* * [ComponentFixture](/api/core/testing/ComponentFixture)
  * [ComponentFixtureAutoDetect](/api/core/testing/ComponentFixtureAutoDetect)
  * [ComponentFixtureNoNgZone](/api/core/testing/ComponentFixtureNoNgZone)
  * [DeferBlockBehavior](/api/core/testing/DeferBlockBehavior)
  * [DeferBlockFixture](/api/core/testing/DeferBlockFixture)
  * [DeferBlockState](/api/core/testing/DeferBlockState)
  * [DirectiveFixture](/api/core/testing/DirectiveFixture)
  * [discardPeriodicTasks](/api/core/testing/discardPeriodicTasks)
  * [fakeAsync](/api/core/testing/fakeAsync)
  * [flush](/api/core/testing/flush)
  * [flushMicrotasks](/api/core/testing/flushMicrotasks)
  * [getTestBed](/api/core/testing/getTestBed)
  * [inject](/api/core/testing/inject)
  * [InjectSetupWrapper](/api/core/testing/InjectSetupWrapper)
  * [MetadataOverride](/api/core/testing/MetadataOverride)
  * [ModuleTeardownOptions](/api/core/testing/ModuleTeardownOptions)
  * [resetFakeAsyncZone](/api/core/testing/resetFakeAsyncZone)
  * [TestBed](/api/core/testing/TestBed)
  * [TestBedStatic](/api/core/testing/TestBedStatic)
  * [TestComponentOptions](/api/core/testing/TestComponentOptions)
  * [TestComponentRenderer](/api/core/testing/TestComponentRenderer)
  * [TestDirectiveOptions](/api/core/testing/TestDirectiveOptions)
  * [TestEnvironmentOptions](/api/core/testing/TestEnvironmentOptions)
  * [TestModuleMetadata](/api/core/testing/TestModuleMetadata)
  * [tick](/api/core/testing/tick)
  * [waitForAsync](/api/core/testing/waitForAsync)
  * [withModule](/api/core/testing/withModule)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](/api/core/testing/TestDirectiveOptions#api)

```
    
      interface TestDirectiveOptions {}
    
    
```

Bindings to apply to the test directive.

Jump to details

----
url: https://angular.dev/api/core/ResourceParamsContext
----

## [API](/api/core/ResourceParamsContext#api)

```
    
      interface ResourceParamsContext {}
    
    
```

Chains the current params off of the value of another resource, returning the value of the other resource only when its status is `resolved` or `local`, or propagating status to the current resource by throwing the appropriate status code when the value is not available.

Jump to details

----
url: https://angular.dev/api/forms/signals/EmailValidationError
----

```
    
      class EmailValidationError extends BaseNgValidationError {}
    
    
```

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/router/NavigationEnd
----

```
    
      class NavigationEnd extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramurlAfterRedirects`string`

@returns`NavigationEnd`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/guide/aria/combobox
----

A directive that coordinates a trigger element (such as a text input, button, or `div`) with a popup, providing the primitive directive for autocomplete, select, and multiselect patterns.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="manual-basic"], app-root:not([theme])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="manual-material"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="material-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.material-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 3rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

.option[aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 50%;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.clear-button:hover {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.clear-button:focus-visible {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 15%, transparent);
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="manual-retro"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="retro-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon" aria-hidden="true">X</span>
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;

  display: flex;
  justify-content: center;
  font-size: 0.6rem;
  font-family: 'Press Start 2P';

  --retro-button-color: #fff;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.retro-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--retro-button-color);
}

.retro-autocomplete:focus-within {
  box-shadow: var(--retro-pressed-shadow);
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: #000;
  z-index: 1;
}

.autocomplete-input {
  width: 15rem;
  font-size: 0.6rem;
  border-radius: 0;
  font-family: 'Press Start 2P';
  word-spacing: -5px;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: #000;
  border: none;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}

.autocomplete-input::placeholder {
  color: #000;
  opacity: 0.7;
}

.popup {
  width: 100%;
  margin-top: 20px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--win95-gray);
  color: var(--win95-shadow);
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  cursor: pointer;
  padding: 0;
  margin: 0;
  outline: none;
  font-family: 'Press Start 2P';
  font-size: 0.6rem;
  user-select: none;
}

.clear-button:hover {
  background: #e0e0e0;
  outline: 1px dashed var(--win95-dark-gray);
  outline-offset: 1px;
}

.clear-button:active {
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
}

.clear-button:focus-visible {
  outline: 2px dashed var(--hot-pink);
  outline-offset: 4px;
}

.clear-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
```

## [Usage](#usage)

Combobox is the primitive directive that coordinates an interactive trigger element (such as a text input, button, or `div`) with a popup. It provides the foundation for autocomplete, select, and multiselect patterns. Consider using combobox directly when:

* **Building custom autocomplete patterns** - Creating specialized filtering or suggestion behavior
* **Creating custom selection components** - Developing dropdowns with unique requirements
* **Coordinating input with popup** - Pairing text input with listbox, tree, or dialog content
* **Implementing custom filtering** - Filtering and orchestrating matching options in user space

Use documented patterns instead when:

* Standard autocomplete with filtering is needed - See the [Autocomplete pattern](guide/aria/autocomplete) for ready-to-use examples
* Single-selection dropdowns are needed - See the [Select pattern](guide/aria/select) for complete dropdown implementation
* Multiple-selection dropdowns are needed - See the [Multiselect pattern](guide/aria/multiselect) for multi-select with compact display

**NOTE:** The [Autocomplete](guide/aria/autocomplete), [Select](guide/aria/select), and [Multiselect](guide/aria/multiselect) guides show documented patterns that combine this directive with [Listbox](guide/aria/listbox) for specific use cases.

## [Features](#features)

Angular's combobox provides a fully accessible input-popup coordination system with:

* **Trigger Element with Popup** - Coordinates trigger element with popup content
* **Flexible Coordination** - Integrates seamlessly with standard layouts (listbox, tree, grid, or dialog)
* **Keyboard Navigation** - Arrow keys, Enter, Escape handling
* **Screen Reader Support** - Built-in ARIA attributes including role="combobox" and aria-expanded
* **Popup Management** - Automatic show/hide based on user interaction
* **Signal-Based Reactivity** - Reactive state management using Angular signals

## [Examples](#examples)

### [Autocomplete](#autocomplete)

An accessible input field that filters and suggests options as users type, helping them find and select values from a list.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-basic"], app-root:not([theme])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-material"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="material-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.material-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 3rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

.option[aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 50%;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.clear-button:hover {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.clear-button:focus-visible {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 15%, transparent);
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-retro"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="retro-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon" aria-hidden="true">X</span>
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;

  display: flex;
  justify-content: center;
  font-size: 0.6rem;
  font-family: 'Press Start 2P';

  --retro-button-color: #fff;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.retro-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--retro-button-color);
}

.retro-autocomplete:focus-within {
  box-shadow: var(--retro-pressed-shadow);
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: #000;
  z-index: 1;
}

.autocomplete-input {
  width: 15rem;
  font-size: 0.6rem;
  border-radius: 0;
  font-family: 'Press Start 2P';
  word-spacing: -5px;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: #000;
  border: none;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}

.autocomplete-input::placeholder {
  color: #000;
  opacity: 0.7;
}

.popup {
  width: 100%;
  margin-top: 20px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--win95-gray);
  color: var(--win95-shadow);
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  cursor: pointer;
  padding: 0;
  margin: 0;
  outline: none;
  font-family: 'Press Start 2P';
  font-size: 0.6rem;
  user-select: none;
}

.clear-button:hover {
  background: #e0e0e0;
  outline: 1px dashed var(--win95-dark-gray);
  outline-offset: 1px;
}

.clear-button:active {
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
}

.clear-button:focus-visible {
  outline: 2px dashed var(--hot-pink);
  outline-offset: 4px;
}

.clear-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
```

Filtering is managed in user space by updating a signal that reactively filters the options list. Users navigate with arrow keys and select with Enter or click. This provides complete control and maximum flexibility for custom selection logic. See the [Autocomplete guide](guide/aria/autocomplete) for complete filtering patterns and examples.

### [Readonly mode](#readonly-mode)

A pattern that combines a readonly combobox with listbox to create single-selection dropdowns with keyboard navigation and screen reader support.

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-basic"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  cursor: pointer;
  padding: 0 3.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-material"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  cursor: pointer;
  height: 3rem;
  padding: 0 3.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-retro"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  cursor: pointer;
  padding: 0 4.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

Triggering a dropdown without text input can be achieved using a button as the host trigger, or applying the native HTML `readonly` attribute to the input trigger. The popup opens on click or arrow keys.

This configuration provides the foundation for the [Select](guide/aria/select) and [Multiselect](guide/aria/multiselect) patterns. See those guides for complete dropdown implementations with triggers and overlay positioning.

### [Datepicker grid](#datepicker-grid)

Combobox can coordinate with a two-dimensional grid to create accessible datepickers. Users navigate dates inside the calendar grid table using directional arrow keys and confirm selection with click, Enter, or Spacebar.

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  computed,
  effect,
  inject,
  signal,
  Signal,
  untracked,
  viewChild,
  viewChildren,
  WritableSignal,
  ElementRef,
} from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MatDateFormats,
  provideNativeDateAdapter,
} from '@angular/material/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {OverlayModule} from '@angular/cdk/overlay';
import {A11yModule} from '@angular/cdk/a11y';

const DAYS_PER_WEEK = 7;

interface CalendarCell<D = any> {
  displayName: string;
  ariaLabel: string;
  date: D;
  selected: boolean;
}

/** @title Combobox with Datepicker Grid. */
@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  providers: [provideNativeDateAdapter()],
  imports: [
    Grid,
    GridRow,
    GridCell,
    GridCellWidget,
    Combobox,
    ComboboxPopup,
    ComboboxWidget,
    OverlayModule,
    A11yModule,
  ],
})
export class App<D> {
  private readonly _dateAdapter = inject<DateAdapter<D>>(DateAdapter, {optional: true})!;
  private readonly _dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS, {optional: true})!;
  private readonly _dayButtons = viewChildren(GridCellWidget); // Dynamic capture of grid cell button widgets

  readonly grid = viewChild(Grid);
  readonly gridTable = viewChild<ElementRef<HTMLElement>>('gridTable');
  readonly comboboxInput = viewChild<ElementRef<HTMLInputElement>>('comboboxInput');

  readonly selection = signal('');
  readonly popupExpanded = signal(false);
  readonly viewMonth: WritableSignal<D> = signal(this._dateAdapter.today());
  private readonly _activeDate: WritableSignal<D> = signal(this._dateAdapter.today());

  // Track the target date that must receive focus post-render
  readonly focusTargetDate = signal<D | null>(null);

  // Helper to identify the current focus target in templates
  isFocusTarget(date: D): boolean {
    const target = this.focusTargetDate();
    return target ? this._dateAdapter.compareDate(date, target) === 0 : false;
  }

  constructor() {
    // Safe, post-render focus restoration loop
    effect(() => {
      const target = this.focusTargetDate();
      if (!target) return;

      // Grab dynamic dependency on day buttons list query
      const buttons = this._dayButtons();

      // Locate the focus button marked with our target attribute
      const targetBtn = buttons.find(
        (btn) => btn.element.getAttribute('data-focus-target') === 'true',
      );

      if (targetBtn) {
        targetBtn.element.focus();

        // Schedule cleanup in separate microtask to avoid circular signal write errors
        Promise.resolve().then(() => {
          untracked(() => this.focusTargetDate.set(null));
        });
      }
    });
  }

  readonly monthYearLabel: Signal<string> = computed(() =>
    this._dateAdapter
      .format(this.viewMonth(), this._dateFormats.display.monthYearLabel)
      .toLocaleUpperCase(),
  );

  readonly activeMonthAnnouncement = computed(
    () =>
      `Showing ${this._dateAdapter.format(this.viewMonth(), this._dateFormats.display.monthYearLabel)}`,
  );

  private readonly _firstWeekOffset: Signal<number> = computed(() => {
    const firstOfMonth = this._dateAdapter.createDate(
      this._dateAdapter.getYear(this.viewMonth()),
      this._dateAdapter.getMonth(this.viewMonth()),
      1,
    );

    return (
      (DAYS_PER_WEEK +
        this._dateAdapter.getDayOfWeek(firstOfMonth) -
        this._dateAdapter.getFirstDayOfWeek()) %
      DAYS_PER_WEEK
    );
  });

  readonly prevMonthNumDays: Signal<number> = computed(() =>
    this._dateAdapter.getNumDaysInMonth(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)),
  );

  readonly daysFromPrevMonth: Signal<number[]> = computed(() => {
    const days: number[] = [];
    for (let i = this._firstWeekOffset() - 1; i >= 0; i--) {
      days.push(this.prevMonthNumDays() - i);
    }
    return days;
  });

  // Calculate the trailing empty days from the next month reactively to complete the final calendar grid week row.
  readonly daysInNextMonth: Signal<number[]> = computed(() => {
    const activeWeeks = this.weeks();
    const lastWeekLength = activeWeeks[activeWeeks.length - 1]?.length || 0;
    const trailingCount = lastWeekLength > 0 ? 7 - lastWeekLength : 0;
    const days: number[] = [];
    for (let i = 1; i <= trailingCount; i++) {
      days.push(i);
    }
    return days;
  });

  // Shift the weekday names array reactively to align with the localized starting day of the week.
  readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => {
    const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
    const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
    const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');

    const weekdays = longWeekdays.map((long, i) => {
      return {long, narrow: narrowWeekdays[i]};
    });
    return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
  });

  // Reconstruct the two-dimensional week-by-week calendar grid whenever the month or selection changes.
  readonly weeks = computed(() => {
    this._activeDate(); // Create dependency on active date
    const viewMonth = this.viewMonth();
    const daysInMonth = this._dateAdapter.getNumDaysInMonth(viewMonth);
    const dateNames = this._dateAdapter.getDateNames();
    const weeks: CalendarCell[][] = [[]];

    for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) {
      if (cell == DAYS_PER_WEEK) {
        weeks.push([]);
        cell = 0;
      }
      const date = this._dateAdapter.createDate(
        this._dateAdapter.getYear(viewMonth),
        this._dateAdapter.getMonth(viewMonth),
        i + 1,
      );
      const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);

      weeks[weeks.length - 1].push({
        displayName: dateNames[i],
        ariaLabel,
        date,
        selected: this._dateAdapter.compareDate(date, this._activeDate()) === 0,
      });
    }
    return weeks;
  });

  nextMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1));
  }

  prevMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1));
  }

  selectDate(cell: CalendarCell<D>, event?: Event): void {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
    const formatted = this._dateAdapter.format(cell.date, this._dateFormats.display.dateInput);
    this.selection.set(formatted);
    this._activeDate.set(cell.date);

    // Synchronously restore focus to the trigger input element before destroying popup to avoid drop
    this.comboboxInput()?.nativeElement.focus();
    this.popupExpanded.set(false);
  }

  // Parse and reconcile dynamic input typing to calendar state
  onInputInput(value: string): void {
    const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput);
    if (parsedDate && this._dateAdapter.isValid(parsedDate)) {
      this._activeDate.set(parsedDate);
      this.viewMonth.set(parsedDate);
    }
  }

  // Handle keyboard inputs on the trigger input field.
  onInputKeydown(event: KeyboardEvent) {
    // Pressing Enter parses the input text and updates the selected date.
    if (event.key === 'Enter') {
      const value = this.selection();
      const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput);
      if (parsedDate && this._dateAdapter.isValid(parsedDate)) {
        this._activeDate.set(parsedDate);
        this.viewMonth.set(parsedDate);
        this.popupExpanded.set(false);
      }
      // Pressing ArrowDown shifts focus into the active cell of the calendar grid.
    } else if (event.key === 'ArrowDown' && this.popupExpanded()) {
      setTimeout(() => {
        const tableEl = this.gridTable()?.nativeElement;
        if (tableEl) {
          const tabbable = tableEl.querySelector('[tabindex="0"]') as HTMLElement;
          (tabbable || tableEl).focus();
        }
      });
    }
  }

  // Safe W3C calendar grid boundaries keys navigation checks
  onGridKeydown(event: KeyboardEvent): void {
    const arrowUp = event.key === 'ArrowUp';
    const arrowDown = event.key === 'ArrowDown';
    const arrowLeft = event.key === 'ArrowLeft';
    const arrowRight = event.key === 'ArrowRight';
    const pageUp = event.key === 'PageUp';
    const pageDown = event.key === 'PageDown';
    const homeKey = event.key === 'Home';
    const endKey = event.key === 'End';

    if (
      !arrowUp &&
      !arrowDown &&
      !arrowLeft &&
      !arrowRight &&
      !pageUp &&
      !pageDown &&
      !homeKey &&
      !endKey
    ) {
      return;
    }

    // Extract the day number of the currently focused button cell
    const targetEl = event.target as HTMLElement;
    const dayAttr = targetEl.getAttribute('data-day');
    if (!dayAttr) return;

    const day = Number(dayAttr);
    const year = this._dateAdapter.getYear(this.viewMonth());
    const month = this._dateAdapter.getMonth(this.viewMonth());
    const viewMonthNumDays = this._dateAdapter.getNumDaysInMonth(this.viewMonth());

    // Reconstitute focused cell date Adapter entity
    const currentFocusedDate = this._dateAdapter.createDate(year, month, day);
    let targetDate: D | null = null;

    // W3C APG Standard calendar keyboard rules
    switch (event.key) {
      case 'ArrowLeft':
        // Day 1 boundary crossing: jump to the last day of the previous month
        if (day === 1) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, -1);
        }
        break;
      case 'ArrowRight':
        // Last day boundary crossing: jump to the first day of the next month
        if (day === viewMonthNumDays) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, 1);
        }
        break;
      case 'ArrowUp':
        // First week boundary crossing: jump back 7 days to the previous month
        if (day <= 7) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, -7);
        }
        break;
      case 'ArrowDown':
        // Last week boundary crossing: jump forward 7 days to the next month
        if (day > viewMonthNumDays - 7) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, 7);
        }
        break;
      case 'PageUp':
        // Shift back 12 months on Control-PageUp, otherwise shift back 1 month
        targetDate = this._dateAdapter.addCalendarMonths(
          currentFocusedDate,
          event.ctrlKey ? -12 : -1,
        );
        break;
      case 'PageDown':
        // Shift forward 12 months on Control-PageDown, otherwise shift forward 1 month
        targetDate = this._dateAdapter.addCalendarMonths(
          currentFocusedDate,
          event.ctrlKey ? 12 : 1,
        );
        break;
      case 'Home':
        // Jump to the 1st of the current month
        targetDate = this._dateAdapter.createDate(year, month, 1);
        break;
      case 'End':
        // Jump to the last day of the current month
        targetDate = this._dateAdapter.createDate(year, month, viewMonthNumDays);
        break;
    }

    if (targetDate) {
      // Mute downstream event listeners inside the grid parent to prevent roving races
      event.preventDefault();
      event.stopImmediatePropagation();
      this.navigateToDate(targetDate);
    }
  }

  navigateToDate(targetDate: D): void {
    const currentMonth = this._dateAdapter.getMonth(this.viewMonth());
    const currentYear = this._dateAdapter.getYear(this.viewMonth());
    const targetMonth = this._dateAdapter.getMonth(targetDate);
    const targetYear = this._dateAdapter.getYear(targetDate);

    const monthShift = currentMonth !== targetMonth || currentYear !== targetYear;

    if (monthShift) {
      // 1. Focus stable table container to stop focus drop to body (prevent overlay crash)
      this.gridTable()?.nativeElement.focus();

      // 2. Reset active grid state synchronously to avoid focus hijacking (Solution B)
      const gridBehavior = this.grid()?._pattern.gridBehavior;
      if (gridBehavior) {
        gridBehavior.focusBehavior.activeCell.set(undefined);
        gridBehavior.focusBehavior.activeCoords.set({row: -1, col: -1});
      }

      // 3. Set target state so the reactive effect knows what to grab post-render
      this.focusTargetDate.set(targetDate);

      // 4. Perform reactive month view transition
      this.viewMonth.set(targetDate);
    } else {
      // Same month traversal: just set the target and the constructor effect will fire immediately
      this.focusTargetDate.set(targetDate);
    }
  }

  handleWidgetKeydown(event: KeyboardEvent) {
    if (event.key === 'Escape') {
      this.comboboxInput()?.nativeElement.focus();
      this.popupExpanded.set(false);
      event.preventDefault();
      event.stopPropagation();
    }
  }
}
```

### app.html

```
<!--
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 -->

<div>
  <div class="example-combobox-container no-active-outline">
    <div #origin class="example-combobox-input-container">
      <span class="material-symbols-outlined example-icon example-search-icon">calendar_month</span>
      <!-- Synchronize typing changes reactively with onInputInput -->
      <input
        #comboboxInput
        ngCombobox
        #combobox="ngCombobox"
        class="example-combobox-input"
        placeholder="Pick a date..."
        [(value)]="selection"
        (input)="onInputInput(comboboxInput.value)"
        [(expanded)]="popupExpanded"
        aria-describedby="date-format-hint"
        (keydown)="onInputKeydown($event)"
        (click)="popupExpanded.set(true)"
      />
    </div>

    <ng-template
      [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: false}"
      [cdkConnectedOverlayOpen]="popupExpanded()"
      (overlayOutsideClick)="popupExpanded.set(false)"
    >
      <ng-template ngComboboxPopup [combobox]="combobox" popupType="dialog">
        <div class="example-popover">
          <!-- Silence CDK focus yanking to protect keyboard typing with [cdkTrapFocusAutoCapture]="false" -->
          <div
            ngComboboxWidget
            class="example-datepicker-popup"
            cdkTrapFocus
            [cdkTrapFocusAutoCapture]="false"
            (keydown)="handleWidgetKeydown($event)"
          >
            <!-- Visually hidden live region for screen reader announcements -->
            <div aria-live="polite" class="cdk-visually-hidden">
              {{ activeMonthAnnouncement() }}
            </div>

            <div class="example-datepicker-header">
              <button
                type="button"
                class="example-datepicker-nav-button"
                (click)="prevMonth()"
                aria-label="Previous Month"
              >
                <span class="material-symbols-outlined">chevron_left</span>
              </button>
              <div class="example-datepicker-title">{{ monthYearLabel() }}</div>
              <button
                type="button"
                class="example-datepicker-nav-button"
                (click)="nextMonth()"
                aria-label="Next Month"
              >
                <span class="material-symbols-outlined">chevron_right</span>
              </button>
            </div>

            <!-- Bind Arrow boundary checks to grid table -->
            <table
              #gridTable
              tabindex="-1"
              ngGrid
              #grid="ngGrid"
              class="example-datepicker-grid"
              colWrap="continuous"
              rowWrap="nowrap"
              [enableSelection]="true"
              selectionMode="explicit"
              (keydown)="onGridKeydown($event)"
            >
              <thead>
                <tr>
                  <!-- Dynamic weekday loop to prevent localization first-day-of-week shifting bug -->
                  @for (day of weekdays(); track day.long) {
                    <th
                      role="columnheader"
                      scope="col"
                      class="example-datepicker-cell example-datepicker-weekday"
                      [attr.abbr]="day.long"
                    >
                      {{ day.narrow }}
                    </th>
                  }
                </tr>
              </thead>

              <tbody>
                @for (week of weeks(); track $index) {
                  <tr ngGridRow>
                    @if ($first) {
                      @for (day of daysFromPrevMonth(); track $index) {
                        <td
                          class="example-datepicker-cell example-datepicker-empty"
                          ngGridCell
                          [disabled]="true"
                          aria-hidden="true"
                          [tabindex]="-1"
                        >
                          {{ day }}
                        </td>
                      }
                    }

                    @for (day of week; track $index) {
                      <td class="example-datepicker-cell" ngGridCell [selected]="day.selected">
                        <!-- Duplicate selected state inside day button to allow clear NVDA/VoiceOver announcements -->
                        <button
                          ngGridCellWidget
                          type="button"
                          class="example-datepicker-day-button"
                          [attr.data-day]="day.displayName"
                          [attr.data-focus-target]="isFocusTarget(day.date)"
                          [attr.aria-label]="day.ariaLabel + (day.selected ? ', Selected' : '')"
                          (click)="selectDate(day, $event)"
                          (keydown.enter)="selectDate(day, $event)"
                          (keydown.space)="selectDate(day, $event)"
                        >
                          {{ day.displayName }}
                        </button>
                      </td>
                    }

                    @if ($last && week.length < 7) {
                      @for (day of daysInNextMonth(); track $index) {
                        <td
                          class="example-datepicker-cell example-datepicker-empty"
                          ngGridCell
                          [disabled]="true"
                          aria-hidden="true"
                          [tabindex]="-1"
                        >
                          {{ day }}
                        </td>
                      }
                    }
                  </tr>
                }
              </tbody>
            </table>
          </div>
        </div>
      </ng-template>
    </ng-template>
  </div>
  <div id="date-format-hint" class="example-combobox-hint">Format: MM/DD/YYYY</div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font, sans-serif);
  --border-color: var(--quaternary-contrast, #e0e0e0);
}

/* Universal/Basic Trigger Layout Styles */
.example-combobox-container {
  position: relative;
  width: 15rem;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--quinary-contrast, #e0e0e0);
  border-radius: 0.25rem;
  background-color: var(--page-background, #ffffff);
  transition:
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.example-combobox-input-container {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 0.25rem;
}

.example-icon {
  width: 24px;
  height: 24px;
  font-size: 24px;
  color: var(--primary-contrast, #1a1a1a);
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.example-search-icon {
  padding: 0 0.5rem;
  position: absolute;
  opacity: 0.8;
}

.example-combobox-input {
  border-radius: 0.25rem;
  width: 100%;
  border: none;
  outline: none;
  font-size: 1rem;
  padding: 0.7rem 1rem 0.7rem 2.5rem;
  background-color: transparent;
  color: var(--primary-contrast, #1a1a1a);
}

.example-combobox-container:focus-within {
  border-color: var(--hot-pink, #ff007f);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink, #ff007f) 20%, transparent);
}

/* Overlay Bounding Popover */
.example-popover {
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-color);
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast, #ffffff);
  overflow: hidden;
}

.example-datepicker-popup {
  padding: 16px;
  width: 320px;
  max-height: none;
  overflow: visible;
  background-color: transparent;
  border: none;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}

.example-datepicker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 12px;
}

.example-datepicker-title {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--primary-contrast, #1a1a1a);
}

.example-datepicker-nav-button {
  background-color: transparent;
  border: none;
  border-radius: 50%;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--primary-contrast, #1a1a1a);
  transition: background-color 0.2s ease;
}

.example-datepicker-nav-button:hover {
  background-color: var(--senary-contrast, #f0f0f0);
}

.example-datepicker-nav-button:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink, #ff007f) 60%, transparent);
}
.example-datepicker-grid {
  width: 100%;
  border-collapse: collapse;
}

.example-datepicker-cell {
  width: 40px;
  height: 40px;
  text-align: center;
  vertical-align: middle;
  padding: 0;
}

.example-datepicker-weekday {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--secondary-contrast, #707070);
  padding-bottom: 8px;
}

.example-datepicker-empty {
  color: var(--senary-contrast, #c0c0c0);
  font-size: 0.8rem;
}

.example-datepicker-day-button {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background-color: transparent;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--primary-contrast, #1a1a1a);
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.example-datepicker-cell:hover .example-datepicker-day-button {
  background-color: var(--senary-contrast, #f0f0f0);
}

.example-datepicker-cell:focus-within {
  outline: 2px solid var(--hot-pink, #ff007f);
  outline-offset: -2px;
}

@media (forced-colors: active) {
  .example-datepicker-cell:focus-within {
    outline: 2px solid CanvasText;
  }
}

.example-datepicker-day-button:focus {
  outline: none;
}

thead {
  background-image: var(
    --pink-to-purple-horizontal-gradient,
    linear-gradient(to right, #ff007f, #6200ee)
  );
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

.example-datepicker-cell[aria-selected='true'] .example-datepicker-day-button {
  background-color: var(--electric-violet, #6200ee);
  color: var(--octonary-contrast, #ffffff);
}

.example-datepicker-nav-button[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

.example-combobox-hint {
  font-size: 0.75rem;
  color: var(--secondary-contrast, #707070);
  margin-top: 4px;
}
```

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  computed,
  effect,
  inject,
  signal,
  Signal,
  untracked,
  viewChild,
  viewChildren,
  WritableSignal,
  ElementRef,
} from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MatDateFormats,
  provideNativeDateAdapter,
} from '@angular/material/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {OverlayModule} from '@angular/cdk/overlay';
import {A11yModule} from '@angular/cdk/a11y';

const DAYS_PER_WEEK = 7;

interface CalendarCell<D = any> {
  displayName: string;
  ariaLabel: string;
  date: D;
  selected: boolean;
}

/** @title Combobox with Datepicker Grid. */
@Component({
  selector: 'app-root:not([theme="basic-material"])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  providers: [provideNativeDateAdapter()],
  imports: [
    Grid,
    GridRow,
    GridCell,
    GridCellWidget,
    Combobox,
    ComboboxPopup,
    ComboboxWidget,
    OverlayModule,
    A11yModule,
  ],
})
export class App<D> {
  private readonly _dateAdapter = inject<DateAdapter<D>>(DateAdapter, {optional: true})!;
  private readonly _dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS, {optional: true})!;
  private readonly _dayButtons = viewChildren(GridCellWidget); // Dynamic capture of grid cell button widgets

  readonly grid = viewChild(Grid);
  readonly gridTable = viewChild<ElementRef<HTMLElement>>('gridTable');
  readonly comboboxInput = viewChild<ElementRef<HTMLInputElement>>('comboboxInput');

  readonly selection = signal('');
  readonly popupExpanded = signal(false);
  readonly viewMonth: WritableSignal<D> = signal(this._dateAdapter.today());
  private readonly _activeDate: WritableSignal<D> = signal(this._dateAdapter.today());

  // Track the target date that must receive focus post-render
  readonly focusTargetDate = signal<D | null>(null);

  // Helper to identify the current focus target in templates
  isFocusTarget(date: D): boolean {
    const target = this.focusTargetDate();
    return target ? this._dateAdapter.compareDate(date, target) === 0 : false;
  }

  constructor() {
    // Safe, post-render focus restoration loop
    effect(() => {
      const target = this.focusTargetDate();
      if (!target) return;

      // Grab dynamic dependency on day buttons list query
      const buttons = this._dayButtons();

      // Locate the focus button marked with our target attribute
      const targetBtn = buttons.find(
        (btn) => btn.element.getAttribute('data-focus-target') === 'true',
      );

      if (targetBtn) {
        targetBtn.element.focus();

        // Schedule cleanup in separate microtask to avoid circular signal write errors
        Promise.resolve().then(() => {
          untracked(() => this.focusTargetDate.set(null));
        });
      }
    });
  }

  readonly monthYearLabel: Signal<string> = computed(() =>
    this._dateAdapter
      .format(this.viewMonth(), this._dateFormats.display.monthYearLabel)
      .toLocaleUpperCase(),
  );

  readonly activeMonthAnnouncement = computed(
    () =>
      `Showing ${this._dateAdapter.format(this.viewMonth(), this._dateFormats.display.monthYearLabel)}`,
  );

  private readonly _firstWeekOffset: Signal<number> = computed(() => {
    const firstOfMonth = this._dateAdapter.createDate(
      this._dateAdapter.getYear(this.viewMonth()),
      this._dateAdapter.getMonth(this.viewMonth()),
      1,
    );

    return (
      (DAYS_PER_WEEK +
        this._dateAdapter.getDayOfWeek(firstOfMonth) -
        this._dateAdapter.getFirstDayOfWeek()) %
      DAYS_PER_WEEK
    );
  });

  readonly prevMonthNumDays: Signal<number> = computed(() =>
    this._dateAdapter.getNumDaysInMonth(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)),
  );

  readonly daysFromPrevMonth: Signal<number[]> = computed(() => {
    const days: number[] = [];
    for (let i = this._firstWeekOffset() - 1; i >= 0; i--) {
      days.push(this.prevMonthNumDays() - i);
    }
    return days;
  });

  // Calculate the trailing empty days from the next month reactively to complete the final calendar grid week row.
  readonly daysInNextMonth: Signal<number[]> = computed(() => {
    const activeWeeks = this.weeks();
    const lastWeekLength = activeWeeks[activeWeeks.length - 1]?.length || 0;
    const trailingCount = lastWeekLength > 0 ? 7 - lastWeekLength : 0;
    const days: number[] = [];
    for (let i = 1; i <= trailingCount; i++) {
      days.push(i);
    }
    return days;
  });

  // Shift the weekday names array reactively to align with the localized starting day of the week.
  readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => {
    const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
    const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
    const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');

    const weekdays = longWeekdays.map((long, i) => {
      return {long, narrow: narrowWeekdays[i]};
    });
    return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
  });

  // Reconstruct the two-dimensional week-by-week calendar grid whenever the month or selection changes.
  readonly weeks = computed(() => {
    this._activeDate(); // Create dependency on active date
    const viewMonth = this.viewMonth();
    const daysInMonth = this._dateAdapter.getNumDaysInMonth(viewMonth);
    const dateNames = this._dateAdapter.getDateNames();
    const weeks: CalendarCell[][] = [[]];

    for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) {
      if (cell == DAYS_PER_WEEK) {
        weeks.push([]);
        cell = 0;
      }
      const date = this._dateAdapter.createDate(
        this._dateAdapter.getYear(viewMonth),
        this._dateAdapter.getMonth(viewMonth),
        i + 1,
      );
      const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);

      weeks[weeks.length - 1].push({
        displayName: dateNames[i],
        ariaLabel,
        date,
        selected: this._dateAdapter.compareDate(date, this._activeDate()) === 0,
      });
    }
    return weeks;
  });

  nextMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1));
  }

  prevMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1));
  }

  selectDate(cell: CalendarCell<D>, event?: Event): void {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
    const formatted = this._dateAdapter.format(cell.date, this._dateFormats.display.dateInput);
    this.selection.set(formatted);
    this._activeDate.set(cell.date);

    // Synchronously restore focus to the trigger input element before destroying popup to avoid drop
    this.comboboxInput()?.nativeElement.focus();
    this.popupExpanded.set(false);
  }

  // Parse and reconcile dynamic input typing to calendar state
  onInputInput(value: string): void {
    const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput);
    if (parsedDate && this._dateAdapter.isValid(parsedDate)) {
      this._activeDate.set(parsedDate);
      this.viewMonth.set(parsedDate);
    }
  }

  // Handle keyboard inputs on the trigger input field.
  onInputKeydown(event: KeyboardEvent) {
    // Pressing Enter parses the input text and updates the selected date.
    if (event.key === 'Enter') {
      const value = this.selection();
      const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput);
      if (parsedDate && this._dateAdapter.isValid(parsedDate)) {
        this._activeDate.set(parsedDate);
        this.viewMonth.set(parsedDate);
        this.popupExpanded.set(false);
      }
      // Pressing ArrowDown shifts focus into the active cell of the calendar grid.
    } else if (event.key === 'ArrowDown' && this.popupExpanded()) {
      setTimeout(() => {
        const tableEl = this.gridTable()?.nativeElement;
        if (tableEl) {
          const tabbable = tableEl.querySelector('[tabindex="0"]') as HTMLElement;
          (tabbable || tableEl).focus();
        }
      });
    }
  }

  // Safe W3C calendar grid boundaries keys navigation checks
  onGridKeydown(event: KeyboardEvent): void {
    const arrowUp = event.key === 'ArrowUp';
    const arrowDown = event.key === 'ArrowDown';
    const arrowLeft = event.key === 'ArrowLeft';
    const arrowRight = event.key === 'ArrowRight';
    const pageUp = event.key === 'PageUp';
    const pageDown = event.key === 'PageDown';
    const homeKey = event.key === 'Home';
    const endKey = event.key === 'End';

    if (
      !arrowUp &&
      !arrowDown &&
      !arrowLeft &&
      !arrowRight &&
      !pageUp &&
      !pageDown &&
      !homeKey &&
      !endKey
    ) {
      return;
    }

    // Extract the day number of the currently focused button cell
    const targetEl = event.target as HTMLElement;
    const dayAttr = targetEl.getAttribute('data-day');
    if (!dayAttr) return;

    const day = Number(dayAttr);
    const year = this._dateAdapter.getYear(this.viewMonth());
    const month = this._dateAdapter.getMonth(this.viewMonth());
    const viewMonthNumDays = this._dateAdapter.getNumDaysInMonth(this.viewMonth());

    // Reconstitute focused cell date Adapter entity
    const currentFocusedDate = this._dateAdapter.createDate(year, month, day);
    let targetDate: D | null = null;

    // W3C APG Standard calendar keyboard rules
    switch (event.key) {
      case 'ArrowLeft':
        // Day 1 boundary crossing: jump to the last day of the previous month
        if (day === 1) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, -1);
        }
        break;
      case 'ArrowRight':
        // Last day boundary crossing: jump to the first day of the next month
        if (day === viewMonthNumDays) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, 1);
        }
        break;
      case 'ArrowUp':
        // First week boundary crossing: jump back 7 days to the previous month
        if (day <= 7) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, -7);
        }
        break;
      case 'ArrowDown':
        // Last week boundary crossing: jump forward 7 days to the next month
        if (day > viewMonthNumDays - 7) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, 7);
        }
        break;
      case 'PageUp':
        // Shift back 12 months on Control-PageUp, otherwise shift back 1 month
        targetDate = this._dateAdapter.addCalendarMonths(
          currentFocusedDate,
          event.ctrlKey ? -12 : -1,
        );
        break;
      case 'PageDown':
        // Shift forward 12 months on Control-PageDown, otherwise shift forward 1 month
        targetDate = this._dateAdapter.addCalendarMonths(
          currentFocusedDate,
          event.ctrlKey ? 12 : 1,
        );
        break;
      case 'Home':
        // Jump to the 1st of the current month
        targetDate = this._dateAdapter.createDate(year, month, 1);
        break;
      case 'End':
        // Jump to the last day of the current month
        targetDate = this._dateAdapter.createDate(year, month, viewMonthNumDays);
        break;
    }

    if (targetDate) {
      // Mute downstream event listeners inside the grid parent to prevent roving races
      event.preventDefault();
      event.stopImmediatePropagation();
      this.navigateToDate(targetDate);
    }
  }

  navigateToDate(targetDate: D): void {
    const currentMonth = this._dateAdapter.getMonth(this.viewMonth());
    const currentYear = this._dateAdapter.getYear(this.viewMonth());
    const targetMonth = this._dateAdapter.getMonth(targetDate);
    const targetYear = this._dateAdapter.getYear(targetDate);

    const monthShift = currentMonth !== targetMonth || currentYear !== targetYear;

    if (monthShift) {
      // 1. Focus stable table container to stop focus drop to body (prevent overlay crash)
      this.gridTable()?.nativeElement.focus();

      // 2. Reset active grid state synchronously to avoid focus hijacking (Solution B)
      const gridBehavior = this.grid()?._pattern.gridBehavior;
      if (gridBehavior) {
        gridBehavior.focusBehavior.activeCell.set(undefined);
        gridBehavior.focusBehavior.activeCoords.set({row: -1, col: -1});
      }

      // 3. Set target state so the reactive effect knows what to grab post-render
      this.focusTargetDate.set(targetDate);

      // 4. Perform reactive month view transition
      this.viewMonth.set(targetDate);
    } else {
      // Same month traversal: just set the target and the constructor effect will fire immediately
      this.focusTargetDate.set(targetDate);
    }
  }

  handleWidgetKeydown(event: KeyboardEvent) {
    if (event.key === 'Escape') {
      this.comboboxInput()?.nativeElement.focus();
      this.popupExpanded.set(false);
      event.preventDefault();
      event.stopPropagation();
    }
  }
}
```

### app.html

```
<!--
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 -->

<div>
  <div class="example-combobox-container no-active-outline">
    <div #origin class="example-combobox-input-container">
      <span class="material-symbols-outlined example-icon example-search-icon">calendar_month</span>
      <!-- Synchronize typing changes reactively with onInputInput -->
      <input
        #comboboxInput
        ngCombobox
        #combobox="ngCombobox"
        class="example-combobox-input"
        placeholder="Pick a date..."
        [(value)]="selection"
        (input)="onInputInput(comboboxInput.value)"
        [(expanded)]="popupExpanded"
        aria-describedby="date-format-hint"
        (keydown)="onInputKeydown($event)"
        (click)="popupExpanded.set(true)"
      />
    </div>

    <ng-template
      [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: false}"
      [cdkConnectedOverlayOpen]="popupExpanded()"
      (overlayOutsideClick)="popupExpanded.set(false)"
    >
      <ng-template ngComboboxPopup [combobox]="combobox" popupType="dialog">
        <div class="example-popover">
          <!-- Silence CDK focus yanking to protect keyboard typing with [cdkTrapFocusAutoCapture]="false" -->
          <div
            ngComboboxWidget
            class="example-datepicker-popup"
            cdkTrapFocus
            [cdkTrapFocusAutoCapture]="false"
            (keydown)="handleWidgetKeydown($event)"
          >
            <!-- Visually hidden live region for screen reader announcements -->
            <div aria-live="polite" class="cdk-visually-hidden">
              {{ activeMonthAnnouncement() }}
            </div>

            <div class="example-datepicker-header">
              <button
                type="button"
                class="example-datepicker-nav-button"
                (click)="prevMonth()"
                aria-label="Previous Month"
              >
                <span class="material-symbols-outlined">chevron_left</span>
              </button>
              <div class="example-datepicker-title">{{ monthYearLabel() }}</div>
              <button
                type="button"
                class="example-datepicker-nav-button"
                (click)="nextMonth()"
                aria-label="Next Month"
              >
                <span class="material-symbols-outlined">chevron_right</span>
              </button>
            </div>

            <!-- Bind Arrow boundary checks to grid table -->
            <table
              #gridTable
              tabindex="-1"
              ngGrid
              #grid="ngGrid"
              class="example-datepicker-grid"
              colWrap="continuous"
              rowWrap="nowrap"
              [enableSelection]="true"
              selectionMode="explicit"
              (keydown)="onGridKeydown($event)"
            >
              <thead>
                <tr>
                  <!-- Dynamic weekday loop to prevent localization first-day-of-week shifting bug -->
                  @for (day of weekdays(); track day.long) {
                    <th
                      role="columnheader"
                      scope="col"
                      class="example-datepicker-cell example-datepicker-weekday"
                      [attr.abbr]="day.long"
                    >
                      {{ day.narrow }}
                    </th>
                  }
                </tr>
              </thead>

              <tbody>
                @for (week of weeks(); track $index) {
                  <tr ngGridRow>
                    @if ($first) {
                      @for (day of daysFromPrevMonth(); track $index) {
                        <td
                          class="example-datepicker-cell example-datepicker-empty"
                          ngGridCell
                          [disabled]="true"
                          aria-hidden="true"
                          [tabindex]="-1"
                        >
                          {{ day }}
                        </td>
                      }
                    }

                    @for (day of week; track $index) {
                      <td class="example-datepicker-cell" ngGridCell [selected]="day.selected">
                        <!-- Duplicate selected state inside day button to allow clear NVDA/VoiceOver announcements -->
                        <button
                          ngGridCellWidget
                          type="button"
                          class="example-datepicker-day-button"
                          [attr.data-day]="day.displayName"
                          [attr.data-focus-target]="isFocusTarget(day.date)"
                          [attr.aria-label]="day.ariaLabel + (day.selected ? ', Selected' : '')"
                          (click)="selectDate(day, $event)"
                          (keydown.enter)="selectDate(day, $event)"
                          (keydown.space)="selectDate(day, $event)"
                        >
                          {{ day.displayName }}
                        </button>
                      </td>
                    }

                    @if ($last && week.length < 7) {
                      @for (day of daysInNextMonth(); track $index) {
                        <td
                          class="example-datepicker-cell example-datepicker-empty"
                          ngGridCell
                          [disabled]="true"
                          aria-hidden="true"
                          [tabindex]="-1"
                        >
                          {{ day }}
                        </td>
                      }
                    }
                  </tr>
                }
              </tbody>
            </table>
          </div>
        </div>
      </ng-template>
    </ng-template>
  </div>
  <div id="date-format-hint" class="example-combobox-hint">Format: MM/DD/YYYY</div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font, sans-serif);
  --border-color: color-mix(in srgb, var(--full-contrast, #000) 20%, var(--page-background, #fff));
}

/* Input Trigger Styles */
.example-combobox-container {
  position: relative;
  width: 15rem;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--quinary-contrast, #e0e0e0);
  border-radius: 3rem;
  background-color: var(--page-background, #ffffff);
  transition:
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.example-combobox-input-container {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 0.25rem;
}

.example-icon {
  width: 24px;
  height: 24px;
  font-size: 24px;
  color: var(--primary-contrast, #1a1a1a);
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.example-search-icon {
  padding: 0 0.5rem;
  position: absolute;
  opacity: 0.8;
}

.example-combobox-input {
  border-radius: 3rem;
  width: 100%;
  border: none;
  outline: none;
  font-size: 1rem;
  padding: 0.7rem 1rem 0.7rem 2.5rem;
  background-color: transparent;
  color: var(--primary-contrast, #1a1a1a);
}

.example-combobox-input::placeholder {
  color: var(--quaternary-contrast, #888888);
}

.example-combobox-container:focus-within {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

/* Overlay Bounding Popover */
.example-popover {
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-color);
  border-radius: 2rem;
  background-color: var(--septenary-contrast, #f5f5f5);
  overflow: hidden;
}

.example-datepicker-popup {
  padding: 16px;
  width: 320px;
  max-height: none;
  overflow: visible;
  background-color: transparent;
  border: none;
  box-shadow: var(--mat-sys-level2-shadow);
}

.example-datepicker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--mat-sys-outline-variant);
  margin-bottom: 12px;
}

.example-datepicker-title {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--mat-sys-on-surface);
}

.example-datepicker-nav-button {
  background-color: transparent;
  border: none;
  border-radius: 50%;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--mat-sys-on-surface);
  transition: background-color 0.2s ease;
}

.example-datepicker-nav-button:hover {
  background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent);
}

.example-datepicker-nav-button:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

.example-datepicker-grid {
  width: 100%;
  border-collapse: collapse;
}

.example-datepicker-cell {
  width: 40px;
  height: 40px;
  text-align: center;
  vertical-align: middle;
  padding: 0;
}

.example-datepicker-weekday {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--mat-sys-on-surface-variant);
  padding-bottom: 8px;
}

.example-datepicker-empty {
  color: color-mix(in srgb, var(--mat-sys-on-surface) 30%, transparent);
  font-size: 0.8rem;
}

.example-datepicker-day-button {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background-color: transparent;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--mat-sys-on-surface);
  transition:
    background-color 0.2s ease,
    color 0.2s ease;
}

.example-datepicker-cell:hover .example-datepicker-day-button {
  background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent);
}

.example-datepicker-cell:focus-within {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

@media (forced-colors: active) {
  .example-datepicker-cell:focus-within {
    outline: 2px solid CanvasText;
  }
}

.example-datepicker-day-button:focus {
  outline: none;
}

.example-datepicker-cell[aria-selected='true'] .example-datepicker-day-button {
  background-color: var(--electric-violet, var(--mat-sys-primary));
  color: var(--octonary-contrast, #ffffff);
}

.example-combobox-hint {
  font-size: 0.75rem;
  color: var(--mat-sys-on-surface-variant);
  margin-top: 4px;
}
```

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {
  Component,
  computed,
  effect,
  inject,
  signal,
  Signal,
  untracked,
  viewChild,
  viewChildren,
  WritableSignal,
  ElementRef,
} from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MatDateFormats,
  provideNativeDateAdapter,
} from '@angular/material/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {OverlayModule} from '@angular/cdk/overlay';
import {A11yModule} from '@angular/cdk/a11y';

const DAYS_PER_WEEK = 7;

interface CalendarCell<D = any> {
  displayName: string;
  ariaLabel: string;
  date: D;
  selected: boolean;
}

/** @title Combobox with Datepicker Grid. */
@Component({
  selector: 'app-root:not([theme="basic-retro"])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  providers: [provideNativeDateAdapter()],
  imports: [
    Grid,
    GridRow,
    GridCell,
    GridCellWidget,
    Combobox,
    ComboboxPopup,
    ComboboxWidget,
    OverlayModule,
    A11yModule,
  ],
})
export class App<D> {
  private readonly _dateAdapter = inject<DateAdapter<D>>(DateAdapter, {optional: true})!;
  private readonly _dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS, {optional: true})!;
  private readonly _dayButtons = viewChildren(GridCellWidget); // Dynamic capture of grid cell button widgets

  readonly grid = viewChild(Grid);
  readonly gridTable = viewChild<ElementRef<HTMLElement>>('gridTable');
  readonly comboboxInput = viewChild<ElementRef<HTMLInputElement>>('comboboxInput');

  readonly selection = signal('');
  readonly popupExpanded = signal(false);
  readonly viewMonth: WritableSignal<D> = signal(this._dateAdapter.today());
  private readonly _activeDate: WritableSignal<D> = signal(this._dateAdapter.today());

  // Track the target date that must receive focus post-render
  readonly focusTargetDate = signal<D | null>(null);

  // Helper to identify the current focus target in templates
  isFocusTarget(date: D): boolean {
    const target = this.focusTargetDate();
    return target ? this._dateAdapter.compareDate(date, target) === 0 : false;
  }

  constructor() {
    // Safe, post-render focus restoration loop
    effect(() => {
      const target = this.focusTargetDate();
      if (!target) return;

      // Grab dynamic dependency on day buttons list query
      const buttons = this._dayButtons();

      // Locate the focus button marked with our target attribute
      const targetBtn = buttons.find(
        (btn) => btn.element.getAttribute('data-focus-target') === 'true',
      );

      if (targetBtn) {
        targetBtn.element.focus();

        // Schedule cleanup in separate microtask to avoid circular signal write errors
        Promise.resolve().then(() => {
          untracked(() => this.focusTargetDate.set(null));
        });
      }
    });
  }

  readonly monthYearLabel: Signal<string> = computed(() =>
    this._dateAdapter
      .format(this.viewMonth(), this._dateFormats.display.monthYearLabel)
      .toLocaleUpperCase(),
  );

  readonly activeMonthAnnouncement = computed(
    () =>
      `Showing ${this._dateAdapter.format(this.viewMonth(), this._dateFormats.display.monthYearLabel)}`,
  );

  private readonly _firstWeekOffset: Signal<number> = computed(() => {
    const firstOfMonth = this._dateAdapter.createDate(
      this._dateAdapter.getYear(this.viewMonth()),
      this._dateAdapter.getMonth(this.viewMonth()),
      1,
    );

    return (
      (DAYS_PER_WEEK +
        this._dateAdapter.getDayOfWeek(firstOfMonth) -
        this._dateAdapter.getFirstDayOfWeek()) %
      DAYS_PER_WEEK
    );
  });

  readonly prevMonthNumDays: Signal<number> = computed(() =>
    this._dateAdapter.getNumDaysInMonth(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)),
  );

  readonly daysFromPrevMonth: Signal<number[]> = computed(() => {
    const days: number[] = [];
    for (let i = this._firstWeekOffset() - 1; i >= 0; i--) {
      days.push(this.prevMonthNumDays() - i);
    }
    return days;
  });

  // Calculate the trailing empty days from the next month reactively to complete the final calendar grid week row.
  readonly daysInNextMonth: Signal<number[]> = computed(() => {
    const activeWeeks = this.weeks();
    const lastWeekLength = activeWeeks[activeWeeks.length - 1]?.length || 0;
    const trailingCount = lastWeekLength > 0 ? 7 - lastWeekLength : 0;
    const days: number[] = [];
    for (let i = 1; i <= trailingCount; i++) {
      days.push(i);
    }
    return days;
  });

  // Shift the weekday names array reactively to align with the localized starting day of the week.
  readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => {
    const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
    const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
    const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');

    const weekdays = longWeekdays.map((long, i) => {
      return {long, narrow: narrowWeekdays[i]};
    });
    return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
  });

  // Reconstruct the two-dimensional week-by-week calendar grid whenever the month or selection changes.
  readonly weeks = computed(() => {
    this._activeDate(); // Create dependency on active date
    const viewMonth = this.viewMonth();
    const daysInMonth = this._dateAdapter.getNumDaysInMonth(viewMonth);
    const dateNames = this._dateAdapter.getDateNames();
    const weeks: CalendarCell[][] = [[]];

    for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) {
      if (cell == DAYS_PER_WEEK) {
        weeks.push([]);
        cell = 0;
      }
      const date = this._dateAdapter.createDate(
        this._dateAdapter.getYear(viewMonth),
        this._dateAdapter.getMonth(viewMonth),
        i + 1,
      );
      const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);

      weeks[weeks.length - 1].push({
        displayName: dateNames[i],
        ariaLabel,
        date,
        selected: this._dateAdapter.compareDate(date, this._activeDate()) === 0,
      });
    }
    return weeks;
  });

  nextMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1));
  }

  prevMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1));
  }

  selectDate(cell: CalendarCell<D>, event?: Event): void {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
    const formatted = this._dateAdapter.format(cell.date, this._dateFormats.display.dateInput);
    this.selection.set(formatted);
    this._activeDate.set(cell.date);

    // Synchronously restore focus to the trigger input element before destroying popup to avoid drop
    this.comboboxInput()?.nativeElement.focus();
    this.popupExpanded.set(false);
  }

  // Parse and reconcile dynamic input typing to calendar state
  onInputInput(value: string): void {
    const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput);
    if (parsedDate && this._dateAdapter.isValid(parsedDate)) {
      this._activeDate.set(parsedDate);
      this.viewMonth.set(parsedDate);
    }
  }

  // Handle keyboard inputs on the trigger input field.
  onInputKeydown(event: KeyboardEvent) {
    // Pressing Enter parses the input text and updates the selected date.
    if (event.key === 'Enter') {
      const value = this.selection();
      const parsedDate = this._dateAdapter.parse(value, this._dateFormats.display.dateInput);
      if (parsedDate && this._dateAdapter.isValid(parsedDate)) {
        this._activeDate.set(parsedDate);
        this.viewMonth.set(parsedDate);
        this.popupExpanded.set(false);
      }
      // Pressing ArrowDown shifts focus into the active cell of the calendar grid.
    } else if (event.key === 'ArrowDown' && this.popupExpanded()) {
      setTimeout(() => {
        const tableEl = this.gridTable()?.nativeElement;
        if (tableEl) {
          const tabbable = tableEl.querySelector('[tabindex="0"]') as HTMLElement;
          (tabbable || tableEl).focus();
        }
      });
    }
  }

  // Safe W3C calendar grid boundaries keys navigation checks
  onGridKeydown(event: KeyboardEvent): void {
    const arrowUp = event.key === 'ArrowUp';
    const arrowDown = event.key === 'ArrowDown';
    const arrowLeft = event.key === 'ArrowLeft';
    const arrowRight = event.key === 'ArrowRight';
    const pageUp = event.key === 'PageUp';
    const pageDown = event.key === 'PageDown';
    const homeKey = event.key === 'Home';
    const endKey = event.key === 'End';

    if (
      !arrowUp &&
      !arrowDown &&
      !arrowLeft &&
      !arrowRight &&
      !pageUp &&
      !pageDown &&
      !homeKey &&
      !endKey
    ) {
      return;
    }

    // Extract the day number of the currently focused button cell
    const targetEl = event.target as HTMLElement;
    const dayAttr = targetEl.getAttribute('data-day');
    if (!dayAttr) return;

    const day = Number(dayAttr);
    const year = this._dateAdapter.getYear(this.viewMonth());
    const month = this._dateAdapter.getMonth(this.viewMonth());
    const viewMonthNumDays = this._dateAdapter.getNumDaysInMonth(this.viewMonth());

    // Reconstitute focused cell date Adapter entity
    const currentFocusedDate = this._dateAdapter.createDate(year, month, day);
    let targetDate: D | null = null;

    // W3C APG Standard calendar keyboard rules
    switch (event.key) {
      case 'ArrowLeft':
        // Day 1 boundary crossing: jump to the last day of the previous month
        if (day === 1) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, -1);
        }
        break;
      case 'ArrowRight':
        // Last day boundary crossing: jump to the first day of the next month
        if (day === viewMonthNumDays) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, 1);
        }
        break;
      case 'ArrowUp':
        // First week boundary crossing: jump back 7 days to the previous month
        if (day <= 7) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, -7);
        }
        break;
      case 'ArrowDown':
        // Last week boundary crossing: jump forward 7 days to the next month
        if (day > viewMonthNumDays - 7) {
          targetDate = this._dateAdapter.addCalendarDays(currentFocusedDate, 7);
        }
        break;
      case 'PageUp':
        // Shift back 12 months on Control-PageUp, otherwise shift back 1 month
        targetDate = this._dateAdapter.addCalendarMonths(
          currentFocusedDate,
          event.ctrlKey ? -12 : -1,
        );
        break;
      case 'PageDown':
        // Shift forward 12 months on Control-PageDown, otherwise shift forward 1 month
        targetDate = this._dateAdapter.addCalendarMonths(
          currentFocusedDate,
          event.ctrlKey ? 12 : 1,
        );
        break;
      case 'Home':
        // Jump to the 1st of the current month
        targetDate = this._dateAdapter.createDate(year, month, 1);
        break;
      case 'End':
        // Jump to the last day of the current month
        targetDate = this._dateAdapter.createDate(year, month, viewMonthNumDays);
        break;
    }

    if (targetDate) {
      // Mute downstream event listeners inside the grid parent to prevent roving races
      event.preventDefault();
      event.stopImmediatePropagation();
      this.navigateToDate(targetDate);
    }
  }

  navigateToDate(targetDate: D): void {
    const currentMonth = this._dateAdapter.getMonth(this.viewMonth());
    const currentYear = this._dateAdapter.getYear(this.viewMonth());
    const targetMonth = this._dateAdapter.getMonth(targetDate);
    const targetYear = this._dateAdapter.getYear(targetDate);

    const monthShift = currentMonth !== targetMonth || currentYear !== targetYear;

    if (monthShift) {
      // 1. Focus stable table container to stop focus drop to body (prevent overlay crash)
      this.gridTable()?.nativeElement.focus();

      // 2. Reset active grid state synchronously to avoid focus hijacking (Solution B)
      const gridBehavior = this.grid()?._pattern.gridBehavior;
      if (gridBehavior) {
        gridBehavior.focusBehavior.activeCell.set(undefined);
        gridBehavior.focusBehavior.activeCoords.set({row: -1, col: -1});
      }

      // 3. Set target state so the reactive effect knows what to grab post-render
      this.focusTargetDate.set(targetDate);

      // 4. Perform reactive month view transition
      this.viewMonth.set(targetDate);
    } else {
      // Same month traversal: just set the target and the constructor effect will fire immediately
      this.focusTargetDate.set(targetDate);
    }
  }

  handleWidgetKeydown(event: KeyboardEvent) {
    if (event.key === 'Escape') {
      this.comboboxInput()?.nativeElement.focus();
      this.popupExpanded.set(false);
      event.preventDefault();
      event.stopPropagation();
    }
  }
}
```

### app.html

```
<!--
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 -->

<div>
  <div class="example-combobox-container no-active-outline">
    <div #origin class="example-combobox-input-container">
      <span class="material-symbols-outlined example-icon example-search-icon">calendar_month</span>
      <!-- Synchronize typing changes reactively with onInputInput -->
      <input
        #comboboxInput
        ngCombobox
        #combobox="ngCombobox"
        class="example-combobox-input"
        placeholder="Pick a date..."
        [(value)]="selection"
        (input)="onInputInput(comboboxInput.value)"
        [(expanded)]="popupExpanded"
        aria-describedby="date-format-hint"
        (keydown)="onInputKeydown($event)"
        (click)="popupExpanded.set(true)"
      />
    </div>

    <ng-template
      [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: false}"
      [cdkConnectedOverlayOpen]="popupExpanded()"
      (overlayOutsideClick)="popupExpanded.set(false)"
    >
      <ng-template ngComboboxPopup [combobox]="combobox" popupType="dialog">
        <div class="example-popover">
          <!-- Silence CDK focus yanking to protect keyboard typing with [cdkTrapFocusAutoCapture]="false" -->
          <div
            ngComboboxWidget
            class="example-datepicker-popup"
            cdkTrapFocus
            [cdkTrapFocusAutoCapture]="false"
            (keydown)="handleWidgetKeydown($event)"
          >
            <!-- Visually hidden live region for screen reader announcements -->
            <div aria-live="polite" class="cdk-visually-hidden">
              {{ activeMonthAnnouncement() }}
            </div>

            <div class="example-datepicker-header">
              <button
                type="button"
                class="example-datepicker-nav-button"
                (click)="prevMonth()"
                aria-label="Previous Month"
              >
                <span class="material-symbols-outlined">chevron_left</span>
              </button>
              <div class="example-datepicker-title">{{ monthYearLabel() }}</div>
              <button
                type="button"
                class="example-datepicker-nav-button"
                (click)="nextMonth()"
                aria-label="Next Month"
              >
                <span class="material-symbols-outlined">chevron_right</span>
              </button>
            </div>

            <!-- Bind Arrow boundary checks to grid table -->
            <table
              #gridTable
              tabindex="-1"
              ngGrid
              #grid="ngGrid"
              class="example-datepicker-grid"
              colWrap="continuous"
              rowWrap="nowrap"
              [enableSelection]="true"
              selectionMode="explicit"
              (keydown)="onGridKeydown($event)"
            >
              <thead>
                <tr>
                  <!-- Dynamic weekday loop to prevent localization first-day-of-week shifting bug -->
                  @for (day of weekdays(); track day.long) {
                    <th
                      role="columnheader"
                      scope="col"
                      class="example-datepicker-cell example-datepicker-weekday"
                      [attr.abbr]="day.long"
                    >
                      {{ day.narrow }}
                    </th>
                  }
                </tr>
              </thead>

              <tbody>
                @for (week of weeks(); track $index) {
                  <tr ngGridRow>
                    @if ($first) {
                      @for (day of daysFromPrevMonth(); track $index) {
                        <td
                          class="example-datepicker-cell example-datepicker-empty"
                          ngGridCell
                          [disabled]="true"
                          aria-hidden="true"
                          [tabindex]="-1"
                        >
                          {{ day }}
                        </td>
                      }
                    }

                    @for (day of week; track $index) {
                      <td class="example-datepicker-cell" ngGridCell [selected]="day.selected">
                        <!-- Duplicate selected state inside day button to allow clear NVDA/VoiceOver announcements -->
                        <button
                          ngGridCellWidget
                          type="button"
                          class="example-datepicker-day-button"
                          [attr.data-day]="day.displayName"
                          [attr.data-focus-target]="isFocusTarget(day.date)"
                          [attr.aria-label]="day.ariaLabel + (day.selected ? ', Selected' : '')"
                          (click)="selectDate(day, $event)"
                          (keydown.enter)="selectDate(day, $event)"
                          (keydown.space)="selectDate(day, $event)"
                        >
                          {{ day.displayName }}
                        </button>
                      </td>
                    }

                    @if ($last && week.length < 7) {
                      @for (day of daysInNextMonth(); track $index) {
                        <td
                          class="example-datepicker-cell example-datepicker-empty"
                          ngGridCell
                          [disabled]="true"
                          aria-hidden="true"
                          [tabindex]="-1"
                        >
                          {{ day }}
                        </td>
                      }
                    }
                  </tr>
                }
              </tbody>
            </table>
          </div>
        </div>
      </ng-template>
    </ng-template>
  </div>
  <div id="date-format-hint" class="example-combobox-hint">Format: MM/DD/YYYY</div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P', Courier, monospace;
  font-size: 0.6rem;
  --border-color: var(--tertiary-contrast, #000000);

  --retro-button-color: var(--septenary-contrast, #ffffff);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #ffffff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--border-color),
    0px 4px 0px 0px var(--border-color), -4px 0px 0px 0px var(--border-color),
    0px -4px 0px 0px var(--border-color);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--border-color), 0px 4px 0px 0px var(--border-color),
    -4px 0px 0px 0px var(--border-color), 0px -4px 0px 0px var(--border-color);
}

/* Input Trigger Styles */
.example-combobox-container {
  position: relative;
  width: 16rem;
  display: flex;
  flex-direction: column;
  border: 4px solid var(--border-color);
  border-radius: 0;
  background-color: var(--retro-button-color);
  box-shadow: var(--retro-flat-shadow);
}

.example-combobox-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.example-icon {
  width: 24px;
  height: 24px;
  font-size: 24px;
  color: var(--border-color);
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.example-search-icon {
  padding: 0 0.5rem;
  position: absolute;
  opacity: 0.8;
}

.example-combobox-input {
  width: 100%;
  border: none;
  outline: none;
  font-size: 0.6rem;
  font-family: 'Press Start 2P', Courier, monospace;
  word-spacing: -4px;
  padding: 0.75rem 1rem 0.75rem 2.5rem;
  background-color: transparent;
  color: var(--primary-contrast, #000000);
}

.example-combobox-container:focus-within {
  outline: 3px solid var(--vivid-pink, #ff007f);
}

/* Overlay Bounding Popover & Dividers */
.example-popover {
  margin: 0;
  padding: 0;
  border: 4px solid var(--border-color);
  background-color: var(--septenary-contrast, #ffffff);
  box-shadow: 8px 8px 0px var(--border-color); /* 3D offset shadow */
}

.example-datepicker-popup {
  padding: 12px;
  width: 320px;
  max-height: none;
  overflow: visible;
  background-color: transparent;
  border: none;
  box-shadow: none; /* Border & shadow handled by popover */
  font-family: 'Press Start 2P', Courier, monospace;
}

.example-datepicker-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 8px;
  border-bottom: 3px double var(--border-color);
  margin-bottom: 12px;
}

.example-datepicker-title {
  font-weight: bold;
  font-size: 0.6rem;
  color: var(--border-color);
}

.example-datepicker-nav-button {
  background-color: var(--retro-button-color);
  border: 2px solid var(--border-color);
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: var(--border-color);
  font-weight: bold;
  box-shadow: 2px 2px 0px var(--border-color);
}

.example-datepicker-nav-button:active {
  transform: translate(2px, 2px);
  box-shadow: none;
}

.example-datepicker-nav-button:focus {
  outline: 2px dashed var(--vivid-pink, #ff007f);
  outline-offset: 2px;
}

.example-datepicker-grid {
  width: 100%;
  border-collapse: collapse;
}

.example-datepicker-cell {
  width: 40px;
  height: 40px;
  text-align: center;
  vertical-align: middle;
  padding: 0;
}

.example-datepicker-weekday {
  font-size: 0.5rem;
  font-weight: bold;
  color: var(--border-color);
  padding-bottom: 8px;
}

.example-datepicker-empty {
  color: var(--senary-contrast, #aaaaaa);
  font-size: 0.5rem;
}

.example-datepicker-day-button {
  width: 32px;
  height: 32px;
  border: 2px solid transparent;
  border-radius: 0;
  background-color: transparent;
  cursor: pointer;
  font-size: 0.6rem;
  font-weight: bold;
  color: var(--border-color);
}

.example-datepicker-cell:hover .example-datepicker-day-button {
  background-color: var(--senary-contrast, #dddddd);
  border-color: var(--border-color);
}

/* Suppress native focus rings and force solid high contrast outlines */
.example-datepicker-cell:focus-within {
  outline: 3px solid var(--border-color);
  outline-offset: -3px;
}

@media (forced-colors: active) {
  .example-datepicker-cell:focus-within {
    outline: 3px solid CanvasText;
  }
}

.example-datepicker-day-button:focus {
  outline: none;
}

thead {
  background-image: var(
    --orange-to-pink-vertical-gradient,
    linear-gradient(to bottom, #ffaa00, #ff007f)
  );
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
}

.example-datepicker-cell[aria-selected='true'] .example-datepicker-day-button {
  background-color: var(--vivid-pink, #ff007f);
  color: var(--page-background, #ffffff);
}

.example-combobox-hint {
  font-size: 0.5rem;
  font-weight: bold;
  font-family: 'Press Start 2P', Courier, monospace;
  color: var(--border-color);
  margin-top: 6px;
}
```

### [Dialog popup](#dialog-popup)

Dialog popups combine the combobox trigger with standard dialog layouts and focus traps (such as CDK's `cdkTrapFocus`). Use dialog popups when the overlay requires modal behavior or backdrop interaction.

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {
  afterRenderEffect,
  Component,
  computed,
  signal,
  viewChild,
  untracked,
  ElementRef,
} from '@angular/core';
import {OverlayModule} from '@angular/cdk/overlay';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  readonly listbox = viewChild<Listbox<string>>(Listbox);
  readonly combobox = viewChild(Combobox);
  readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');

  readonly value = signal('');
  readonly searchString = signal('');
  readonly selectedCountries = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly options = computed(() =>
    ALL_COUNTRIES.filter((country) =>
      country.toLowerCase().startsWith(this.searchString().toLowerCase()),
    ),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.popupExpanded()) {
        untracked(() => {
          setTimeout(() => {
            this.searchInput()?.nativeElement.focus();
          });
        });
      }
    });

    afterRenderEffect(() => {
      if (this.popupExpanded()) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedCountries();
    if (selected.length > 0) {
      this.value.set(selected[0]);
      this.searchString.set('');
      this.popupExpanded.set(false);
      this.combobox()?.element.focus();
    }
  }

  onSearchEscape(event: Event) {
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Brazil',
  'Canada',
  'Egypt',
  'France',
  'Germany',
  'India',
  'Japan',
  'Mexico',
  'United Kingdom',
  'United States of America',
];
```

### app.html

```
<!--
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 -->

<div
  class="example-combobox-container"
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
>
  <div #origin class="example-combobox-input-container">
    <input
      class="example-combobox-input example-dialog-input"
      placeholder="Select a country..."
      [value]="value()"
      [readonly]="true"
      [tabindex]="-1"
    />
    <span class="material-symbols-outlined example-icon example-arrow-icon" aria-hidden="true"
      >arrow_drop_down</span
    >
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
    [cdkConnectedOverlayDisableClose]="true"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="example-popover">
        <div class="example-dialog" ngComboboxWidget>
          <div class="example-combobox-container">
            <!-- Inner Combobox (Editable Search Trigger) -->
            <div class="example-combobox-input-container">
              <span
                class="material-symbols-outlined example-icon example-search-icon"
                aria-hidden="true"
                >search</span
              >
              <input
                ngCombobox
                #innerCombobox="ngCombobox"
                #searchInput
                class="example-combobox-input"
                placeholder="Search..."
                [(ngModel)]="searchString"
                [alwaysExpanded]="true"
                (keydown.escape)="onSearchEscape($event)"
              />
            </div>

            <div aria-live="polite" class="cdk-visually-hidden">
              {{ options().length === 0 ? 'No results found for ' + searchString() : '' }}
            </div>

            <!-- Connected Popup wrapping the options listbox -->
            <ng-template ngComboboxPopup [combobox]="innerCombobox">
              <div class="example-popup example-popup-no-margin">
                @if (options().length === 0) {
                  <div class="example-no-results">No results found</div>
                }
                <div
                  #listbox="ngListbox"
                  ngListbox
                  ngComboboxWidget
                  class="example-listbox"
                  focusMode="activedescendant"
                  tabindex="-1"
                  selectionMode="explicit"
                  [(value)]="selectedCountries"
                  (click)="onCommit()"
                  (keydown.enter)="onCommit()"
                  [activeDescendant]="listbox.activeDescendant()"
                  [class.example-empty]="options().length === 0"
                >
                  @for (option of options(); track option) {
                    <div
                      class="example-option example-selectable example-stateful"
                      ngOption
                      [value]="option"
                      [label]="option"
                    >
                      <span>{{ option }}</span>
                      <span
                        aria-hidden="true"
                        class="material-symbols-outlined example-icon example-selected-icon"
                        >check</span
                      >
                    </div>
                  }
                </div>
              </div>
            </ng-template>
          </div>
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font, system-ui, sans-serif);
  --border-color: color-mix(in srgb, var(--full-contrast, #000) 20%, var(--page-background, #fff));
}

.example-combobox-container {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-color);
  border-radius: 0.25rem;
  transition:
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.example-combobox-container:has(.example-dialog-input) {
  width: 15rem;
  border-radius: 0.25rem;
  background-color: var(--page-background, #ffffff);
  border: 1px solid var(--quinary-contrast, #e0e0e0);
  color: var(--primary-contrast, #1a1a1a);
}

.example-combobox-input-container {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 0.25rem;
}

.example-combobox-input {
  border-radius: 0.25rem;
  width: 100%;
  border: none;
  outline: none;
  font-size: 1rem;
  padding: 0.7rem 1rem 0.7rem 2.5rem;
  background-color: var(--septenary-contrast, #f5f5f5);
  color: var(--primary-contrast, #1a1a1a);
}

.example-combobox-input::-webkit-search-cancel-button,
.example-combobox-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.example-combobox-input.example-dialog-input {
  cursor: pointer;
  text-align: left;
  padding: 0 3.5rem 0 1rem;
  height: 2.5rem;
  background-color: transparent;
  color: inherit;
  font-weight: 500;
  border: none;
  outline: none;
}

.example-combobox-container:focus-within {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 25%, transparent);
}

.example-icon {
  width: 24px;
  height: 24px;
  font-size: 24px;
  color: var(--primary-contrast, #1a1a1a);
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.example-search-icon {
  padding: 0 0.5rem;
  position: absolute;
  opacity: 0.8;
}

.example-arrow-icon {
  padding: 0 0.5rem;
  position: absolute;
  right: 0;
  opacity: 0.8;
  transition: transform 0.2s ease;
}

.example-combobox-input[aria-expanded='true'] + .example-arrow-icon {
  transform: rotate(180deg);
}

.example-popover {
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-color);
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast, #f5f5f5);
  overflow: hidden;
}

.example-popup {
  width: 100%;
  margin-block-start: 0;
  border: none;
  border-radius: 0;
  background-color: transparent;
  max-height: 15rem;
  height: auto;
}

.example-popup.example-popup-no-margin {
  margin-block-start: 0;
}

.example-listbox {
  display: flex;
  flex-direction: column;
  overflow: auto;
  max-height: 13rem;
  height: auto;
  box-sizing: border-box;
  padding: 0.5rem;
  gap: 4px;
  outline: none;
}

.example-option {
  cursor: pointer;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  min-height: 2.25rem;
  box-sizing: border-box;
  display: flex;
  overflow: hidden;
  flex-shrink: 0;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  color: var(--primary-contrast, #1a1a1a);
}

.example-selected-icon {
  visibility: hidden;
  font-size: 0.9rem;
}

.example-option[aria-selected='true'] .example-selected-icon {
  visibility: visible;
}

.example-option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast, #1a1a1a) 5%, transparent);
}

.example-combobox-container:not(.no-active-outline):focus-within
  [data-active='true']:not(.no-active-outline),
.example-option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

@media (forced-colors: active) {
  .example-combobox-container:not(.no-active-outline):focus-within
    [data-active='true']:not(.no-active-outline),
  .example-option[data-active='true'] {
    outline: 2px solid CanvasText;
  }
}

.example-dialog {
  position: relative;
  padding: 0;
  background-color: var(--septenary-contrast, #f5f5f5);
  width: 15rem;
  box-sizing: border-box;
}

.example-dialog .example-combobox-container {
  border: none;
  border-radius: inherit;
  box-shadow: none;
  background-color: transparent;
}

.example-dialog .example-combobox-input-container {
  border-bottom: 1px solid var(--border-color);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.example-no-results {
  padding: 1rem;
}

.example-empty {
  position: absolute;
  visibility: hidden;
  pointer-events: none;
  height: 0;
  width: 0;
  overflow: hidden;
}

.example-dialog-input[disabled],
.example-dialog-input[aria-disabled='true'] {
  opacity: 0.6;
  cursor: not-allowed;
}
```

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {
  afterRenderEffect,
  Component,
  computed,
  signal,
  viewChild,
  untracked,
  ElementRef,
} from '@angular/core';
import {OverlayModule} from '@angular/cdk/overlay';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root:not([theme="basic-material"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  readonly listbox = viewChild<Listbox<string>>(Listbox);
  readonly combobox = viewChild(Combobox);
  readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');

  readonly value = signal('');
  readonly searchString = signal('');
  readonly selectedCountries = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly options = computed(() =>
    ALL_COUNTRIES.filter((country) =>
      country.toLowerCase().startsWith(this.searchString().toLowerCase()),
    ),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.popupExpanded()) {
        untracked(() => {
          setTimeout(() => {
            this.searchInput()?.nativeElement.focus();
          });
        });
      }
    });

    afterRenderEffect(() => {
      if (this.popupExpanded()) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedCountries();
    if (selected.length > 0) {
      this.value.set(selected[0]);
      this.searchString.set('');
      this.popupExpanded.set(false);
      this.combobox()?.element.focus();
    }
  }

  onSearchEscape(event: Event) {
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Brazil',
  'Canada',
  'Egypt',
  'France',
  'Germany',
  'India',
  'Japan',
  'Mexico',
  'United Kingdom',
  'United States of America',
];
```

### app.html

```
<!--
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 -->

<div
  class="example-combobox-container"
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
>
  <div #origin class="example-combobox-input-container">
    <input
      class="example-combobox-input example-dialog-input"
      placeholder="Select a country..."
      [value]="value()"
      [readonly]="true"
      [tabindex]="-1"
    />
    <span class="material-symbols-outlined example-icon example-arrow-icon" aria-hidden="true"
      >arrow_drop_down</span
    >
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
    [cdkConnectedOverlayDisableClose]="true"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="example-popover">
        <div class="example-dialog" ngComboboxWidget>
          <div class="example-combobox-container">
            <!-- Inner Combobox (Editable Search Trigger) -->
            <div class="example-combobox-input-container">
              <span
                class="material-symbols-outlined example-icon example-search-icon"
                aria-hidden="true"
                >search</span
              >
              <input
                ngCombobox
                #innerCombobox="ngCombobox"
                #searchInput
                class="example-combobox-input"
                placeholder="Search..."
                [(ngModel)]="searchString"
                [alwaysExpanded]="true"
                (keydown.escape)="onSearchEscape($event)"
              />
            </div>

            <div aria-live="polite" class="cdk-visually-hidden">
              {{ options().length === 0 ? 'No results found for ' + searchString() : '' }}
            </div>

            <!-- Connected Popup wrapping the options listbox -->
            <ng-template ngComboboxPopup [combobox]="innerCombobox">
              <div class="example-popup example-popup-no-margin">
                @if (options().length === 0) {
                  <div class="example-no-results">No results found</div>
                }
                <div
                  #listbox="ngListbox"
                  ngListbox
                  ngComboboxWidget
                  class="example-listbox"
                  focusMode="activedescendant"
                  tabindex="-1"
                  selectionMode="explicit"
                  [(value)]="selectedCountries"
                  (click)="onCommit()"
                  (keydown.enter)="onCommit()"
                  [activeDescendant]="listbox.activeDescendant()"
                  [class.example-empty]="options().length === 0"
                >
                  @for (option of options(); track option) {
                    <div
                      class="example-option example-selectable example-stateful"
                      ngOption
                      [value]="option"
                      [label]="option"
                    >
                      <span>{{ option }}</span>
                      <span
                        aria-hidden="true"
                        class="material-symbols-outlined example-icon example-selected-icon"
                        >check</span
                      >
                    </div>
                  }
                </div>
              </div>
            </ng-template>
          </div>
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font, system-ui, sans-serif);
  --border-color: color-mix(in srgb, var(--full-contrast, #000) 20%, var(--page-background, #fff));
}

.example-combobox-container {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-color);
  border-radius: 0.25rem;
  transition:
    box-shadow 0.2s ease,
    border-color 0.2s ease;
}

.example-combobox-container:has(.example-dialog-input) {
  width: 15rem;
  border-radius: 3rem;
  background-color: var(--page-background, #ffffff);
  border: 1px solid var(--quinary-contrast, #e0e0e0);
  color: var(--primary-contrast, #1a1a1a);
}

.example-combobox-input-container {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 0.25rem;
}

.example-combobox-input {
  border-radius: 0.25rem;
  width: 100%;
  border: none;
  outline: none;
  font-size: 1rem;
  padding: 0.7rem 1rem 0.7rem 2.5rem;
  background-color: var(--septenary-contrast, #f5f5f5);
  color: var(--primary-contrast, #1a1a1a);
}

.example-combobox-input::-webkit-search-cancel-button,
.example-combobox-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.example-combobox-input.example-dialog-input {
  cursor: pointer;
  text-align: left;
  padding: 0 3.5rem 0 1.5rem;
  height: 3rem;
  background-color: transparent;
  color: inherit;
  font-weight: 500;
  border: none;
  outline: none;
}

.example-combobox-container:focus-within {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 25%, transparent);
}

.example-icon {
  width: 24px;
  height: 24px;
  font-size: 24px;
  color: var(--primary-contrast, #1a1a1a);
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  display: grid;
  place-items: center;
  pointer-events: none;
}

.example-search-icon {
  padding: 0 0.5rem;
  position: absolute;
  opacity: 0.8;
}

.example-arrow-icon {
  padding: 0 0.5rem;
  position: absolute;
  right: 1rem;
  opacity: 0.9;
  color: var(--primary-contrast, #1a1a1a);
  transition: transform 0.2s ease;
}

.example-combobox-input[aria-expanded='true'] + .example-arrow-icon {
  transform: rotate(180deg);
}

.example-popover {
  margin: 0;
  padding: 0;
  border: 1px solid var(--border-color);
  border-radius: 2rem;
  background-color: var(--septenary-contrast, #f5f5f5);
  overflow: hidden;
}

.example-popup {
  width: 100%;
  margin-block-start: 0;
  border: none;
  border-radius: 0;
  background-color: transparent;
  max-height: 15rem;
  height: auto;
}

.example-popup.example-popup-no-margin {
  margin-block-start: 0;
}

.example-listbox {
  display: flex;
  flex-direction: column;
  overflow: auto;
  max-height: 13rem;
  height: auto;
  box-sizing: border-box;
  padding: 0.5rem;
  gap: 4px;
  outline: none;
}

.example-option {
  cursor: pointer;
  padding: 0 1.25rem;
  border-radius: 3rem;
  min-height: 3rem;
  box-sizing: border-box;
  display: flex;
  overflow: hidden;
  flex-shrink: 0;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  color: var(--primary-contrast, #1a1a1a);
}

.example-selected-icon {
  visibility: hidden;
  font-size: 0.9rem;
}

.example-option[aria-selected='true'] .example-selected-icon {
  visibility: visible;
}

.example-option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}

.example-option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast, #1a1a1a) 5%, transparent);
}

.example-combobox-container:not(.no-active-outline):focus-within
  [data-active='true']:not(.no-active-outline),
.example-option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

@media (forced-colors: active) {
  .example-combobox-container:not(.no-active-outline):focus-within
    [data-active='true']:not(.no-active-outline),
  .example-option[data-active='true'] {
    outline: 2px solid CanvasText;
  }
}

.example-dialog {
  position: relative;
  padding: 0;
  background-color: transparent;
  width: 15rem;
  box-sizing: border-box;
}

.example-dialog .example-combobox-container {
  border: none;
  border-radius: inherit;
  box-shadow: none;
  background-color: transparent;
  padding: 10px;
}

.example-dialog .example-combobox-input-container {
  border-bottom: 1px solid var(--border-color);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.example-no-results {
  padding: 1rem;
}

.example-empty {
  position: absolute;
  visibility: hidden;
  pointer-events: none;
  height: 0;
  width: 0;
  overflow: hidden;
}

.example-dialog-input[disabled],
.example-dialog-input[aria-disabled='true'] {
  opacity: 0.6;
  cursor: not-allowed;
}
```

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {
  afterRenderEffect,
  Component,
  computed,
  signal,
  viewChild,
  untracked,
  ElementRef,
} from '@angular/core';
import {OverlayModule} from '@angular/cdk/overlay';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root:not([theme="basic-retro"])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  readonly listbox = viewChild<Listbox<string>>(Listbox);
  readonly combobox = viewChild(Combobox);
  readonly searchInput = viewChild<ElementRef<HTMLInputElement>>('searchInput');

  readonly value = signal('');
  readonly searchString = signal('');
  readonly selectedCountries = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly options = computed(() =>
    ALL_COUNTRIES.filter((country) =>
      country.toLowerCase().startsWith(this.searchString().toLowerCase()),
    ),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.popupExpanded()) {
        untracked(() => {
          setTimeout(() => {
            this.searchInput()?.nativeElement.focus();
          });
        });
      }
    });

    afterRenderEffect(() => {
      if (this.popupExpanded()) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedCountries();
    if (selected.length > 0) {
      this.value.set(selected[0]);
      this.searchString.set('');
      this.popupExpanded.set(false);
      this.combobox()?.element.focus();
    }
  }

  onSearchEscape(event: Event) {
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Brazil',
  'Canada',
  'Egypt',
  'France',
  'Germany',
  'India',
  'Japan',
  'Mexico',
  'United Kingdom',
  'United States of America',
];
```

### app.html

```
<!--
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 -->

<div
  class="example-combobox-container"
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
>
  <div #origin class="example-combobox-input-container">
    <input
      class="example-combobox-input example-dialog-input"
      placeholder="Select a country..."
      [value]="value()"
      [readonly]="true"
      [tabindex]="-1"
    />
    <span class="material-symbols-outlined example-icon example-arrow-icon" aria-hidden="true"
      >arrow_drop_down</span
    >
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
    [cdkConnectedOverlayDisableClose]="true"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="example-popover">
        <div class="example-dialog" ngComboboxWidget>
          <div class="example-combobox-container">
            <!-- Inner Combobox (Editable Search Trigger) -->
            <div class="example-combobox-input-container">
              <span
                class="material-symbols-outlined example-icon example-search-icon"
                aria-hidden="true"
                >search</span
              >
              <input
                ngCombobox
                #innerCombobox="ngCombobox"
                #searchInput
                class="example-combobox-input"
                placeholder="Search..."
                [(ngModel)]="searchString"
                [alwaysExpanded]="true"
                (keydown.escape)="onSearchEscape($event)"
              />
            </div>

            <div aria-live="polite" class="cdk-visually-hidden">
              {{ options().length === 0 ? 'No results found for ' + searchString() : '' }}
            </div>

            <!-- Connected Popup wrapping the options listbox -->
            <ng-template ngComboboxPopup [combobox]="innerCombobox">
              <div class="example-popup example-popup-no-margin">
                @if (options().length === 0) {
                  <div class="example-no-results">No results found</div>
                }
                <div
                  #listbox="ngListbox"
                  ngListbox
                  ngComboboxWidget
                  class="example-listbox"
                  focusMode="activedescendant"
                  tabindex="-1"
                  selectionMode="explicit"
                  [(value)]="selectedCountries"
                  (click)="onCommit()"
                  (keydown.enter)="onCommit()"
                  [activeDescendant]="listbox.activeDescendant()"
                  [class.example-empty]="options().length === 0"
                >
                  @for (option of options(); track option) {
                    <div
                      class="example-option example-selectable example-stateful"
                      ngOption
                      [value]="option"
                      [label]="option"
                    >
                      <span>{{ option }}</span>
                      <span
                        aria-hidden="true"
                        class="material-symbols-outlined example-icon example-selected-icon"
                        >check</span
                      >
                    </div>
                  }
                </div>
              </div>
            </ng-template>
          </div>
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P', Courier, monospace;
  font-size: 0.6rem;
  --border-color: var(--tertiary-contrast, #000000);

  --retro-button-color: var(--septenary-contrast, #ffffff);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #ffffff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--border-color),
    0px 4px 0px 0px var(--border-color), -4px 0px 0px 0px var(--border-color),
    0px -4px 0px 0px var(--border-color);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--border-color), 0px 4px 0px 0px var(--border-color),
    -4px 0px 0px 0px var(--border-color), 0px -4px 0px 0px var(--border-color);
}

.example-combobox-container {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  border: 4px solid var(--border-color);
  border-radius: 0;
  background-color: var(--retro-button-color);
}

.example-combobox-container:has(.example-dialog-input) {
  width: 16rem;
  box-shadow: var(--retro-flat-shadow);
}

.example-combobox-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.example-combobox-input {
  width: 100%;
  border: none;
  outline: none;
  font-size: 0.6rem;
  font-family: 'Press Start 2P', Courier, monospace;
  word-spacing: -4px;
  padding: 0.75rem 1rem 0.75rem 2.5rem;
  background-color: transparent;
  color: var(--primary-contrast, #000000);
}

.example-combobox-input::-webkit-search-cancel-button,
.example-combobox-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.example-combobox-input.example-dialog-input {
  cursor: pointer;
  text-align: left;
  padding: 0.75rem 1rem;
}

.example-combobox-container:focus-within {
  outline: 3px solid var(--vivid-pink);
}

@media (forced-colors: active) {
  .example-combobox-container:focus-within {
    outline: 3px solid CanvasText;
  }
}

.example-icon {
  width: 24px;
  height: 24px;
  font-size: 20px;
  color: var(--primary-contrast, #000000);
  display: grid;
  place-items: center;
  pointer-events: none;
}

.example-search-icon {
  padding: 0 0.5rem;
  position: absolute;
  opacity: 0.8;
}

.example-arrow-icon {
  padding: 0 0.5rem;
  position: absolute;
  right: 0;
  opacity: 0.8;
}

.example-popover {
  margin: 0;
  padding: 0;
  border: 4px solid var(--border-color);
  background-color: var(--septenary-contrast, #f5f5f5);
  box-shadow: 8px 8px 0px var(--border-color); /* 3D offset shadow */
}

.example-popup {
  width: 100%;
  background-color: transparent;
  max-height: 15rem;
  height: auto;
}

.example-popup.example-popup-no-margin {
  margin-block-start: 0;
}

.example-listbox {
  display: flex;
  flex-direction: column;
  overflow: auto;
  max-height: 13rem;
  height: auto;
  box-sizing: border-box;
  padding: 0.5rem;
  gap: 4px;
  outline: none;
}

.example-option {
  cursor: pointer;
  padding: 0.3rem 1rem;
  display: flex;
  overflow: hidden;
  flex-shrink: 0;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  color: var(--primary-contrast, #000000);
  font-size: 0.6rem;
  border: 2px solid transparent;
}

.example-selected-icon {
  visibility: hidden;
  font-size: 0.9rem;
}

.example-option[aria-selected='true'] .example-selected-icon {
  visibility: visible;
}

.example-option[aria-selected='true'] {
  color: var(--page-background, #ffffff);
  background-color: var(--vivid-pink);
}

.example-option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast, #1a1a1a) 10%, transparent);
  border-color: var(--border-color);
}

.example-option[data-active='true'] {
  outline: 3px dashed var(--vivid-pink);
  outline-offset: -3px;
}

@media (forced-colors: active) {
  .example-option[data-active='true'] {
    outline: 3px solid CanvasText;
  }
}

.example-dialog {
  position: relative;
  padding: 0;
  background-color: transparent;
  width: 16rem;
  box-sizing: border-box;
}

.example-dialog .example-combobox-input-container {
  border-bottom: 3px double var(--border-color);
}

.example-no-results {
  padding: 1rem;
  font-weight: bold;
}

.example-empty {
  position: absolute;
  visibility: hidden;
  pointer-events: none;
  height: 0;
  width: 0;
  overflow: hidden;
}

.example-dialog-input[disabled],
.example-dialog-input[aria-disabled='true'] {
  opacity: 0.6;
  cursor: not-allowed;
}
```

## [Testing](#testing)

Angular Aria provides a `ComboboxHarness` for testing combobox components. Here is an example of how to use the harness in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComboboxHarness} from '@angular/aria/combobox/testing';
import {MyComboboxComponent} from './my-combobox'; // Your component

describe('MyComboboxComponent', () => {
  let fixture: ComponentFixture<MyComboboxComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyComboboxComponent],
    });

    fixture = TestBed.createComponent(MyComboboxComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should allow opening and closing the popup', async () => {
    const combobox = await loader.getHarness(ComboboxHarness);

    // Verify initial state
    expect(await combobox.isOpen()).toBe(false);

    // Open the popup
    await combobox.open();
    expect(await combobox.isOpen()).toBe(true);

    // Close the popup
    await combobox.close();
    expect(await combobox.isOpen()).toBe(false);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Combobox`](/api/aria/combobox/Combobox)
* [`ComboboxPopup`](/api/aria/combobox/ComboboxPopup)
* [`ComboboxWidget`](/api/aria/combobox/ComboboxWidget)

### [Related patterns and directives](#related-patterns-and-directives)

Combobox is the primitive directive for these documented patterns:

* [Autocomplete](guide/aria/autocomplete) - Filtering and suggestions pattern (coordinates input typing with options list)
* [Select](guide/aria/select) - Single selection dropdown pattern (applied directly on non-editable button triggers)
* [Multiselect](guide/aria/multiselect) - Multiple selection pattern (applied on non-editable triggers with multi-enabled Listbox)

Combobox typically combines with:

* [Listbox](guide/aria/listbox) - Most common popup content
* [Tree](guide/aria/tree) - Hierarchical popup content (see Tree guide for examples)

----
url: https://angular.dev/api/core/testing/getTestBed
----

```
function getTestBed(): TestBed;
```

@returns`TestBed`

Jump to details

----
url: https://angular.dev/api/cdk/testing/HarnessLoader
----

```
    
      interface HarnessLoader {}
    
    
```

Searches for an element with the given selector under the current instances's root element, and returns a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) rooted at the matching element. If multiple elements match the selector, the first is used. If no elements match, an error is thrown.

@paramselector`string`

The selector for the root element of the new [`HarnessLoader`](/api/cdk/testing/HarnessLoader)

@returns`Promise<HarnessLoader>`

Searches for all elements with the given selector under the current instances's root element, and returns an array of [`HarnessLoader`](/api/cdk/testing/HarnessLoader)s, one for each matching element, rooted at that element.

@paramselector`string`

The selector for the root element of the new [`HarnessLoader`](/api/cdk/testing/HarnessLoader)

@returns`Promise<HarnessLoader[]>`

Searches for an instance of the component corresponding to the given harness type under the [`HarnessLoader`](/api/cdk/testing/HarnessLoader)'s root element, and returns a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for that instance. If multiple matching components are found, a harness for the first one is returned. If no matching component is found, an error is thrown.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<T>`

Searches for an instance of the component corresponding to the given harness type under the [`HarnessLoader`](/api/cdk/testing/HarnessLoader)'s root element, and returns a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for that instance. If multiple matching components are found, a harness for the first one is returned. If no matching component is found, null is returned.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<T | null>`

Searches for an instance of the component corresponding to the given harness type under the [`HarnessLoader`](/api/cdk/testing/HarnessLoader)'s root element, and returns a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for the instance on the page at the given index. If no matching component exists at that index, an error is thrown.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@paramindex`number`

The zero-indexed offset of the matching component instance to return

@returns`Promise<T>`

Searches for all instances of the component corresponding to the given harness type under the [`HarnessLoader`](/api/cdk/testing/HarnessLoader)'s root element, and returns a list [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for each instance.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<T[]>`

Searches for all instances of the component corresponding to the given harness type under the [`HarnessLoader`](/api/cdk/testing/HarnessLoader)'s root element, and returns the total count of all matching components.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<number>`

Searches for an instance of the component corresponding to the given harness type under the [`HarnessLoader`](/api/cdk/testing/HarnessLoader)'s root element, and returns a boolean indicating if any were found.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<boolean>`

Jump to details

----
url: https://angular.dev/api/core/IterableDifferFactory
----

```
    
      interface IterableDifferFactory {}
    
    
```

@paramobjects`any`

@returns`boolean`

@paramtrackByFn`TrackByFunction<V> | undefined`

@returns`IterableDiffer<V>`

Jump to details

----
url: https://angular.dev/reference/migrations/ngclass-to-class
----

This schematic migrates NgClass directive usages to class bindings in your application. It will only migrate usages that are considered safe to migrate.

Run the schematic using the following command:

```
ng generate @angular/core:ngclass-to-class
```

#### [Before](#before)

```
<div [ngClass]="{admin: isAdmin, dense: density === 'high'}"></div>
```

#### [After](#after)

```
<div [class]="{admin: isAdmin, dense: density === 'high'}"></div>
```

## [Configuration options](#configuration-options)

The migration supports a few options for fine tuning the migration to your specific needs.

### [`--migrate-space-separated-key`](#--migrate-space-separated-key)

By default, the migration avoids migrating usages of [`NgClass`](/api/common/NgClass) in which object literal keys contain space-separated class names. When the --migrate-space-separated-key flag is enabled, a binding is created for each individual key.

```
<div [ngClass]="{'class1 class2': condition}"></div>
```

to

```
<div [class.class1]="condition" [class.class2]="condition"></div>
```

----
url: https://angular.dev/api/cdk/drag-drop/DragRef
----

```
    
      class DragRef<T = any> {}
    
    
```

@paramelement`any`

@param\_config`DragRefConfig`

@param\_document`Document`

@param\_ngZone`NgZone`

@param\_viewportRuler`ViewportRuler`

@param\_dragDropRegistry`DragDropRegistry`

@param\_renderer`Renderer2`

@returns`DragRef<T>`

Axis along which dragging is locked.

Amount of milliseconds to wait after the user has put their pointer down before starting to drag the element.

Class to be added to the preview element.

If the parent of the dragged element has a `scale` transform, it can throw off the positioning when the user starts dragging. Use this input to notify the CDK of the scale.

Whether starting to drag this element is disabled.

Emits as the drag sequence is being prepared.

Arbitrary data that can be attached to the drag item.

Function that can be used to customize the logic of how the position of the drag item is limited while it's being dragged. Gets called with a point containing the current position of the user's pointer on the page, a reference to the item being dragged and its dimensions. Should return a point describing where the item should be rendered.

Returns the element that is being used as a placeholder while the current element is being dragged.

@returns`HTMLElement`

Returns the root draggable element.

@returns`HTMLElement`

Gets the currently-visible element that represents the drag item. While dragging this is the placeholder, otherwise it's the root element.

@returns`HTMLElement`

Registers the handles that can be used to drag the element.

@paramhandles`any[]`

@returns`this`

Registers the template that should be used for the drag preview.

@paramtemplate`DragPreviewTemplate<any> | null`

Template that from which to stamp out the preview.

@returns`this`

Registers the template that should be used for the drag placeholder.

@paramtemplate`DragHelperTemplate<any> | null`

Template that from which to stamp out the placeholder.

@returns`this`

Sets an alternate drag root element. The root element is the element that will be moved as the user is dragging. Passing an alternate root element is useful when trying to enable dragging on an element that you might not have access to.

@paramrootElement`any`

@returns`this`

Element to which the draggable's position will be constrained.

@paramboundaryElement`any`

@returns`this`

Sets the parent ref that the ref is nested in.

@paramparent`DragRef<unknown> | null`

@returns`this`

Removes the dragging functionality from the DOM element.

@returns`void`

Checks whether the element is currently being dragged.

@returns`boolean`

Resets a standalone drag item to its initial position.

@returns`void`

Resets drag item to end of boundary element.

@returns`void`

Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.

@paramhandle`HTMLElement`

Handle element that should be disabled.

@returns`void`

Enables a handle, if it has been disabled.

@paramhandle`HTMLElement`

Handle element to be enabled.

@returns`void`

Sets the layout direction of the draggable item.

@paramdirection`Direction`

@returns`this`

Gets the current position in pixels the draggable outside of a drop container.

@returns`Readonly<Point>`

Sets the current position in pixels the draggable outside of a drop container.

@paramvalue`Point`

New position to be set.

@returns`this`

Sets the container into which to insert the preview element.

@paramvalue`any`

Container into which to insert the preview.

@returns`this`

Jump to details

----
url: https://angular.dev/api/core/ng-template
----

Angular's `<ng-template>` element defines a template that is not rendered by default.

With `<ng-template>`, you can define template content that is only being rendered by Angular when you, whether directly or indirectly, specifically instruct it to do so, allowing you to have full control over how and when the content is displayed.

Note that if you wrap content inside an `<ng-template>` without instructing Angular to render it, such content will not appear on a page. For example, see the following HTML code, when handling it Angular won't render the middle "Hip!" in the phrase "Hip! Hip! Hooray!" because of the surrounding `<ng-template>`.

```
<p>Hip!</p>
<ng-template>
  <p>Hip!</p>
</ng-template>
<p>Hooray!</p>
```

## [Usage notes](#usage-notes)

### [Structural Directives](#structural-directives)

One of the main uses for `<ng-template>` is to hold template content that will be used by [Structural directives](guide/directives/structural-directives). Those directives can add and remove copies of the template content based on their own logic.

When using the [structural directive shorthand](guide/directives/structural-directives#structural-directive-shorthand), Angular creates an `<ng-template>` element behind the scenes.

### [TemplateRef](#templateref)

`<ng-template>` elements are represented as instances of the `TemplateRef` class.

To add copies of the template to the DOM, pass this object to the `ViewContainerRef` method `createEmbeddedView()`.

### [Template Variables](#template-variables)

`<ng-template>` elements can be referenced in templates using [standard template variables](guide/templates/variables#assigning-values-to-template-reference-variables).

*This is how `<ng-template>` elements are used as `ngIf` else clauses.*

Such template variables can be used in conjunction with `ngTemplateOutlet` directives to render the content defined inside `<ng-template>` tags.

### [Querying](#querying)

A [Query](api/core/Query) (such as `ViewChild`) can find the `TemplateRef` associated to an `<ng-template>` element so that it can be used programmatically; for instance, to pass it to the `ViewContainerRef` method `createEmbeddedView()`.

### [Context](#context)

Inside the `<ng-template>` tags you can reference variables present in the surrounding outer template. Additionally, a context object can be associated with `<ng-template>` elements. Such an object contains variables that can be accessed from within the template contents via template (`let` and `as`) declarations.

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/async/provideAnimationsAsync
----

* * [provideAnimationsAsync](/api/platform-browser/animations/async/provideAnimationsAsync)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

Deprecation warning

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

## [API](#api)

```
function provideAnimationsAsync(
  type?: 'animations' | 'noop',
): EnvironmentProviders;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramtype`"animations" | "noop"`

pass `'noop'` as argument to disable animations.

@returns`EnvironmentProviders`

## [Description](#description)

Returns the set of dependency-injection providers to enable animations in an application. See [animations guide](guide/animations) to learn more about animations in Angular.

When you use this function instead of the eager `provideAnimations()`, animations won't be rendered until the renderer is loaded.

## [Usage Notes](#usage-notes)

The function is useful when you want to enable animations in an application bootstrapped using the `bootstrapApplication` function. In this scenario there is no need to import the `BrowserAnimationsModule` NgModule at all, just add providers returned by this function to the `providers` list as show below.

```
bootstrapApplication(RootComponent, {
  providers: [
    provideAnimationsAsync()
  ]
});
```

Jump to details

----
url: https://angular.dev/api/common/getCurrencySymbol
----

Angular recommends relying on the `Intl` API for i18n. You can use `Intl.NumberFormat().formatToParts()` to extract the currency symbol. For example: `Intl.NumberFormat('en', {style:'currency', currency: 'USD'}).formatToParts().find(part => part.type === 'currency').value` returns `$` for USD currency code in the `en` locale. Note: `US$` is a currency symbol for the `en-ca` locale but not the `en-us` locale.

## [API](#api)

```
function getCurrencySymbol(
  code: string,
  format: 'wide' | 'narrow',
  locale?: string,
): string;
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. You can use `Intl.NumberFormat().formatToParts()` to extract the currency symbol. For example: `Intl.NumberFormat('en', {style:'currency', currency: 'USD'}).formatToParts().find(part => part.type === 'currency').value` returns `$` for USD currency code in the `en` locale. Note: `US$` is a currency symbol for the `en-ca` locale but not the `en-us` locale.

@paramcode`string`

The currency code.

@paramformat`"wide" | "narrow"`

The format, `wide` or `narrow`.

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`string`

The symbol, or the currency code if no symbol is available.

## [Description](#description)

Retrieves the currency symbol for a given currency code.

For example, for the default `en-US` locale, the code `USD` can be represented by the narrow symbol `$` or the wide symbol `US$`.

Jump to details

----
url: https://angular.dev/api/ssr/ServerRouteClient
----

```
    
      interface ServerRouteClient extends ServerRouteCommon {}
    
    
```

Specifies that the route uses Client-Side Rendering (CSR) mode.

The path associated with this route.

Optional additional headers to include in the response for this route.

Optional status code to return for this route.

Jump to details

----
url: https://angular.dev/api/forms/signals/FormFieldBindingOptions
----

## [API](/api/forms/signals/FormFieldBindingOptions#api)

```
    
      interface FormFieldBindingOptions {}
    
    
```

Focuses the binding.

If not specified, Signal Forms will attempt to focus the host element of the [`FormField`](/api/forms/signals/FormField) when asked to focus this binding.

Resets the binding.

Jump to details

----
url: https://angular.dev/api/forms/signals/isFieldTree
----

## [API](/api/forms/signals/isFieldTree#api)

```
function isFieldTree(value: unknown): value is FieldTree<unknown>;
```

@paramvalue`unknown`

Value to be checked.

@returns`value is FieldTree<unknown>`

Jump to details

----
url: https://angular.dev/api/router/withExperimentalPlatformNavigation
----

```
function withExperimentalPlatformNavigation(): ExperimentalPlatformNavigationFeature;
```

@returns`ExperimentalPlatformNavigationFeature`

A [`RouterFeature`](/api/router/RouterFeature) that enables the platform navigation.

## [Description](#description)

**CRITICAL:** This feature is *highly* experimental and should not be used in production. Browser support is limited and in active development. Use only for experimentation and feedback purposes.

This function provides a [`Location`](/api/common/Location) strategy that uses the browser's [`Navigation`](/api/router/Navigation) API. By using the platform's Navigation APIs, the Router is able to provide native browser navigation capabilities. Some advantages include:

* The ability to intercept navigations triggered outside the Router. This allows plain anchor elements *without* [`RouterLink`](/api/router/RouterLink) directives to be intercepted by the Router and converted to SPA navigations.
* Native scroll and focus restoration support by the browser, without the need for custom implementations.
* Communication of ongoing navigations to the browser, enabling built-in features like accessibility announcements, loading indicators, stop buttons, and performance measurement APIs.

**NOTE:** Deferred entry updates are not part of the interop 2025 Navigation API commitments so the "ongoing navigation" communication support is limited.

## [Usage Notes](#usage-notes)

```
const appRoutes: Routes = [
  { path: 'page', component: PageComponent },
];

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(appRoutes, withExperimentalPlatformNavigation())
  ]
});
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MIN
----

```
    
      const MIN: LimitSelectionKey;
    
    
```

## [Description](#description)

A [`MetadataKey`](/api/forms/signals/MetadataKey) that points to another key determining the minimum value of the field.

This indirection allows different keys to be used for different types of values with their own reducers, such as [`MIN_DATE`](/api/forms/signals/MIN_DATE) and [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER).

Jump to details

----
url: https://angular.dev/api/common/IMAGE_CONFIG
----

```
    
      const IMAGE_CONFIG: InjectionToken<ImageConfig>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/TrackByFunction
----

```
    
      interface TrackByFunction<T> {  <U extends T>(index: number, item: T & U): any;}
    
    
```

@paramindex`number`

The index of the item within the iterable.

@paramitem`T & U`

The item in the iterable.

@returns`any`

## [Description](#description)

A function optionally passed into the `NgForOf` directive to customize how `NgForOf` uniquely identifies items in an iterable.

`NgForOf` needs to uniquely identify items in the iterable to correctly perform DOM updates when items in the iterable are reordered, new items are added, or existing items are removed.

In all of these scenarios it is usually desirable to only update the DOM elements associated with the items affected by the change. This behavior is important to:

* preserve any DOM-specific UI state (like cursor position, focus, text selection) when the iterable is modified
* enable animation of item addition, removal, and iterable reordering
* preserve the value of the `<select>` element when nested `<option>` elements are dynamically populated using `NgForOf` and the bound iterable is updated

A common use for custom `trackBy` functions is when the model that `NgForOf` iterates over contains a property with a unique identifier. For example, given a model:

```
class User {
  id: number;
  name: string;
  ...
}
```

a custom `trackBy` function could look like the following:

```
function userTrackBy(index, user) {
  return user.id;
}
```

A custom `trackBy` function must have several properties:

* be [idempotent](https://en.wikipedia.org/wiki/Idempotence) (be without side effects, and always return the same value for a given input)
* return unique value for all unique inputs
* be fast

Jump to details

----
url: https://angular.dev/api/core/outputBinding
----

```
function outputBinding<T>(
  eventName: string,
  listener: (event: T) => unknown,
): Binding;
```

@parameventName`string`

Public name of the output to listen to.

@paramlistener`(event: T) => unknown`

Function to be called when the output emits.

### [Usage example](#usage-example)

In this example we create an instance of the `MyCheckbox` component and listen to its `onChange` event.

```
interface CheckboxChange {
value: string;
}

createComponent(MyCheckbox, {
bindings: [
outputBinding<CheckboxChange>('onChange', event => console.log(event.value))
],
});
```

@returns`Binding`

Jump to details

----
url: https://angular.dev/api/core/testing/inject
----

```
function inject(tokens: any[], fn: Function): () => any;
```

@paramtokens`any[]`

@paramfn`Function`

@returns`() => any`

## [Description](#description)

Allows injecting dependencies in `beforeEach()` and `it()`. Note: this function (imported from the `@angular/core/testing` package) can **only** be used to inject dependencies in tests. To inject dependencies in your application code, use the [`inject`](api/core/inject) function from the `@angular/core` package instead.

Example:

```
beforeEach(inject([Dependency, AClass], (dep, object) => {
  // some code that uses `dep` and `object`
  // ...
}));

it('...', inject([AClass], (object) => {
  object.doSomething();
  expect(...);
})
```

Jump to details

----
url: https://angular.dev/api/platform-browser/withHttpTransferCacheOptions
----

```
function withHttpTransferCacheOptions(
  options: HttpTransferCacheOptions,
): HydrationFeature<HydrationFeatureKind.HttpTransferCacheOptions>;
```

@paramoptions`HttpTransferCacheOptions`

@returns`HydrationFeature<HydrationFeatureKind.HttpTransferCacheOptions>`

Jump to details

----
url: https://angular.dev/api/common/testing/SpyLocation
----

```
    
      class SpyLocation implements Location {}
    
    
```

@paramurl`string`

@returns`void`

@paramurl`string`

@returns`void`

@returns`string`

@returns`unknown`

@parampath`string`

@paramquery`string`

@returns`boolean`

@parampathname`string`

@returns`void`

@parampathname`string`

@returns`void`

@paramurl`string`

@returns`string`

@parampath`string`

@paramquery`string`

@paramstate`any`

@returns`void`

@parampath`string`

@paramquery`string`

@paramstate`any`

@returns`void`

@returns`void`

@returns`void`

@paramrelativePosition`number`

@returns`void`

@paramfn`(url: string, state: unknown) => void`

@returns`VoidFunction`

@paramonNext`(value: any) => void`

@paramonThrow`((error: any) => void) | null | undefined`

@paramonReturn`(() => void) | null | undefined`

@returns`SubscriptionLike`

@paramurl`string`

@returns`string`

Jump to details

----
url: https://angular.dev/api/forms/signals/maxDateError
----

```
function maxDateError(
  maxDate: Date,
  options: WithFieldTree<ValidationErrorOptions>,
): MaxDateValidationError;
function maxDateError(
  maxDate: Date,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<MaxDateValidationError>;
```

Create a maxDate error associated with the target field

@parammaxDate`Date`

The max date constraint

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`MaxDateValidationError`

Create a maxDate error

@parammaxDate`Date`

The max date constraint

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<MaxDateValidationError>`

Jump to details

----
url: https://angular.dev/api/router/NavigationSkipped
----

```
    
      class NavigationSkipped extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramreason`string`

A description of why the navigation was skipped. For debug purposes only. Use `code` instead for a stable skipped reason that can be used in production.

@paramcode`NavigationSkippedCode | undefined`

A code to indicate why the navigation was skipped. This code is stable for the reason and can be relied on whereas the `reason` string could change and should not be used in production.

@returns`NavigationSkipped`

A description of why the navigation was skipped. For debug purposes only. Use `code` instead for a stable skipped reason that can be used in production.

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/api/router/RouterConfigOptions
----

## [API](/api/router/RouterConfigOptions#api)

```
    
      interface RouterConfigOptions {}
    
    
```

----
url: https://angular.dev/api/animations/animateChild
----

```
function animateChild(
  options?: AnimateChildOptions | null,
): AnimationAnimateChildMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramoptions`AnimateChildOptions | null`

An options object that can contain a delay value for the start of the animation, and additional override values for developer-defined parameters.

@returns`AnimationAnimateChildMetadata`

## [Usage Notes](#usage-notes)

Each time an animation is triggered in Angular, the parent animation has priority and any child animations are blocked. In order for a child animation to run, the parent animation must query each of the elements containing child animations, and run them using this function.

Note that this feature is designed to be used with [`query()`](/api/animations/query) and it will only work with animations that are assigned using the Angular animation library. CSS keyframes and transitions are not handled by this API.

Jump to details

----
url: https://angular.dev/api/core/DestroyableInjector
----

```
    
      interface DestroyableInjector extends Injector {
}
    
    
```

@returns`void`

Jump to details

----
url: https://angular.dev/api/upgrade/static/UpgradeComponent
----

* * [downgradeComponent](/api/upgrade/static/downgradeComponent)
  * [downgradeInjectable](/api/upgrade/static/downgradeInjectable)
  * [downgradeModule](/api/upgrade/static/downgradeModule)
  * [getAngularJSGlobal](/api/upgrade/static/getAngularJSGlobal)
  * [setAngularJSGlobal](/api/upgrade/static/setAngularJSGlobal)
  * [UpgradeComponent](/api/upgrade/static/UpgradeComponent)
  * [UpgradeModule](/api/upgrade/static/UpgradeModule)
  * [VERSION](/api/upgrade/static/VERSION)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](/api/upgrade/static/UpgradeComponent#api)

```
    
      class UpgradeComponent implements OnInit ,OnChanges ,DoCheck ,OnDestroy {}
    
    
```

Create a new [`UpgradeComponent`](/api/upgrade/static/UpgradeComponent) instance. You should not normally need to do this. Instead you should derive a new class from this one and call the super constructor from the base class.

```
// This Angular directive will act as an interface to the "upgraded" AngularJS component
@Directive({
  selector: 'ng1-hero',
  standalone: false,
})
export class Ng1HeroComponentWrapper extends UpgradeComponent {
  // The names of the input and output properties here must match the names of the
  // `<` and `&` bindings in the AngularJS component that is being wrapped
  hero = input.required<Hero>();
  onRemove = output<void>();

  constructor(elementRef: ElementRef, injector: Injector) {
    // We must pass the name of the directive as used by AngularJS to the super
    super('ng1Hero', elementRef, injector);
  }
}
```

* The `name` parameter should be the name of the AngularJS directive.
* The `elementRef` and `injector` parameters should be acquired from Angular by dependency injection into the base class constructor.

@paramname`string`

@paramelementRef`ElementRef<any>`

@paraminjector`Injector`

@returns`UpgradeComponent`

## [Description](/api/upgrade/static/UpgradeComponent#description)

A helper class that allows an AngularJS component to be used from Angular.

*Part of the [upgrade/static](api?query=upgrade%2Fstatic) library for hybrid upgrade apps that support AOT compilation.*

This helper class should be used as a base class for creating Angular directives that wrap AngularJS components that need to be "upgraded".

## [Usage Notes](/api/upgrade/static/UpgradeComponent#usage-notes)

### [Examples](/api/upgrade/static/UpgradeComponent#examples)

Let's assume that you have an AngularJS component called `ng1Hero` that needs to be made available in Angular templates.

```
// This AngularJS component will be "upgraded" to be used in Angular
ng1AppModule.component('ng1Hero', {
  bindings: {hero: '<', onRemove: '&'},
  transclude: true,
  template: `<div class="title" ng-transclude></div>
             <h2>{{ $ctrl.hero.name }}</h2>
             <p>{{ $ctrl.hero.description }}</p>
             <button ng-click="$ctrl.onRemove()">Remove</button>`,
});
```

We must create a [`Directive`](/api/core/Directive) that will make this AngularJS component available inside Angular templates.

```
// This Angular directive will act as an interface to the "upgraded" AngularJS component
@Directive({
  selector: 'ng1-hero',
  standalone: false,
})
export class Ng1HeroComponentWrapper extends UpgradeComponent {
  // The names of the input and output properties here must match the names of the
  // `<` and `&` bindings in the AngularJS component that is being wrapped
  hero = input.required<Hero>();
  onRemove = output<void>();

  constructor(elementRef: ElementRef, injector: Injector) {
    // We must pass the name of the directive as used by AngularJS to the super
    super('ng1Hero', elementRef, injector);
  }
}
```

In this example you can see that we must derive from the [`UpgradeComponent`](/api/upgrade/static/UpgradeComponent) base class but also provide an [``](/api/core/Directive)`@Directive` decorator. This is because the AOT compiler requires that this information is statically available at compile time.

Note that we must do the following:

* specify the directive's selector (`ng1-hero`)

* specify all inputs and outputs that the AngularJS component expects

* derive from [`UpgradeComponent`](/api/upgrade/static/UpgradeComponent)

* call the base class from the constructor, passing

  * the AngularJS name of the component (`ng1Hero`)
  * the [`ElementRef`](/api/core/ElementRef) and [`Injector`](/api/core/Injector) for the component wrapper

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8105
----

This diagnostic is emitted when an expression used in `*ngFor` is missing the `let` keyword.

```
import {Component} from '@angular/core';

@Component({
  // The `let` keyword is missing in the `*ngFor` expression.
  template: `<div *ngFor="item of items">{{ item }}</div>`,
})
class MyComponent {
  items = [1, 2, 3];
}
```

## [What's wrong with that?](#whats-wrong-with-that)

A missing `let` is indicative of a syntax error in the `*ngFor` string. It also means that `item` will not be properly pulled into scope and `{{ item }}` will not resolve correctly.

## [What should I do instead?](#what-should-i-do-instead)

Add the missing `let` keyword.

```
import {Component} from '@angular/core';

@Component({
  // The `let` keyword is now present in the `*ngFor` expression,
  // no diagnostic messages are emitted in this case.
  template: `<div *ngFor="let item of items">{{ item }}</div>`,
})
class MyComponent {
  items = [1, 2, 3];
}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `missingNgForOfLet` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "missingNgForOfLet": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/core/testing/TestBed
----

```
    
      interface TestBed {
}
    
    
```

----
url: https://angular.dev/api/core/getModuleFactory
----

Use [`getNgModuleById`](/api/core/getNgModuleById) instead.

## [API](#api)

```
function getModuleFactory(id: string): NgModuleFactory<any>;
```

@deprecated

Use [`getNgModuleById`](/api/core/getNgModuleById) instead.

@paramid`string`

@returns`NgModuleFactory<any>`

Jump to details

----
url: https://angular.dev/api/ssr/node/AngularNodeAppEngine
----

```
    
      class AngularNodeAppEngine {}
    
    
```

Creates a new instance of the Angular Node.js server application engine.

@paramoptions`AngularNodeAppEngineOptions | undefined`

Options for the Angular Node.js server application engine.

@returns`AngularNodeAppEngine`

Handles an incoming HTTP request by serving prerendered content, performing server-side rendering, or delivering a static file for client-side rendered routes based on the `RenderMode` setting.

This method adapts Node.js's `IncomingMessage`, `Http2ServerRequest` or `Request` to a format compatible with the `AngularAppEngine` and delegates the handling logic to it.

@paramrequest`IncomingMessage | Http2ServerRequest | Request`

* The incoming HTTP request (`IncomingMessage`, `Http2ServerRequest` or `Request`).

Resolution: Authorize your hostname by configuring `allowedHosts` in `angular.json` in: `projects.[project-name].architect.build.options.security.allowedHosts`. Alternatively, you can define the allowed hostname via the environment variable `process.env['NG_ALLOWED_HOSTS']` or pass it directly through the configuration options of [`AngularNodeAppEngine`](/api/ssr/node/AngularNodeAppEngine).

For more information see: <https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf>

## [Usage Notes](#usage-notes)

This class should be instantiated once and used as a singleton across the server-side application to ensure consistent handling of rendering requests and resource management.

Jump to details

----
url: https://angular.dev/api/aria/grid/GridCell
----

```
    
      class GridCell implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

Whether the cell is currently active (focused).

Text direction.

A unique identifier for the cell.

The ARIA role for the cell.

The number of rows the cell should span.

The number of columns the cell should span.

The index of this cell's row within the grid.

The index of this cell's column within the grid.

Whether the cell is disabled.

Whether the cell is selected.

Whether the cell is selectable.

The tabindex override.

@returns`void`

@returns`void`

## [Description](#description)

Represents a cell within a grid row. It is the primary focusable element within the grid. It can be disabled and can have its selection state managed through the `selected` input.

```
<td ngGridCell [disabled]="isDisabled" [(selected)]="isSelected">
  Cell Content
</td>
```

Jump to details

----
url: https://angular.dev/api/core/resource
----

```
function resource<T, R>(
  options: ResourceOptions<T, R> & { defaultValue: NoInfer<T> },
): ResourceRef<T>;
function resource<T, R>(
  options: ResourceOptions<T, R>,
): ResourceRef<T | undefined>;
```

Constructs a [`Resource`](/api/core/Resource) that projects a reactive request to an asynchronous operation defined by a loader function, which exposes the result of the loading operation via signals.

Note that [`resource`](/api/core/resource) is intended for *read* operations, not operations which perform mutations. [`resource`](/api/core/resource) will cancel in-progress loads via the `AbortSignal` when destroyed or when a new request object becomes available, which could prematurely abort mutations.

@paramoptions`ResourceOptions<T, R> & { defaultValue: NoInfer<T>; }`

@returns`ResourceRef<T>`

Constructs a [`Resource`](/api/core/Resource) that projects a reactive request to an asynchronous operation defined by a loader function, which exposes the result of the loading operation via signals.

Note that [`resource`](/api/core/resource) is intended for *read* operations, not operations which perform mutations. [`resource`](/api/core/resource) will cancel in-progress loads via the `AbortSignal` when destroyed or when a new request object becomes available, which could prematurely abort mutations.

@paramoptions`ResourceOptions<T, R>`

@returns`ResourceRef<T | undefined>`

## [Description](#description)

Constructs a [`Resource`](/api/core/Resource) that projects a reactive request to an asynchronous operation defined by a loader function, which exposes the result of the loading operation via signals.

Note that [`resource`](/api/core/resource) is intended for *read* operations, not operations which perform mutations. [`resource`](/api/core/resource) will cancel in-progress loads via the `AbortSignal` when destroyed or when a new request object becomes available, which could prematurely abort mutations.

Jump to details

----
url: https://angular.dev/api/elements/NgElement
----

```
    
      abstract class NgElement extends HTMLElement {
}
    
    
```

The strategy that controls how a component is transformed in a custom element.

A subscription to change, connect, and disconnect events in the custom element.

Prototype for a handler that responds to a change in an observed attribute.

@paramattrName`string`

The name of the attribute that has changed.

@paramoldValue`string | null`

The previous value of the attribute.

@paramnewValue`string`

The new value of the attribute.

@paramnamespace`string | undefined`

The namespace in which the attribute is defined.

@returns`void`

Nothing.

Prototype for a handler that responds to the insertion of the custom element in the DOM.

@returns`void`

Nothing.

Prototype for a handler that responds to the deletion of the custom element from the DOM.

@returns`void`

Nothing.

The **`HTMLElement.accessKey`** property sets the keystroke which a user can press to jump to a given element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/accessKey)

The **`HTMLElement.accessKeyLabel`** read-only property returns a string containing the element's browser-assigned access key (if any); otherwise it returns an empty string.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/accessKeyLabel)

The **`autocapitalize`** property of the HTMLElement interface represents the element's capitalization behavior for user input. It is available on all HTML elements, though it doesn't affect all of them, including:

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/autocapitalize)

The **`autocorrect`** property of the HTMLElement interface controls whether or not autocorrection of editable text is enabled for spelling and/or punctuation errors.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/autocorrect)

The **`HTMLElement.dir`** property indicates the text writing directionality of the content of the current element. It reflects the element's dir attribute.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dir)

The **`draggable`** property of the HTMLElement interface gets and sets a Boolean primitive indicating if the element is draggable.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/draggable)

The HTMLElement property **`hidden`** reflects the value of the element's hidden attribute.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/hidden)

The HTMLElement property **`inert`** reflects the value of the element's inert attribute. It is a boolean value that, when present, makes the browser "ignore" user input events for the element, including focus events and events from assistive technologies. The browser may also ignore page search and text selection in the element. This can be useful when building UIs such as modals where you would want to "trap" the focus inside the modal when it's visible.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/inert)

The **`innerText`** property of the HTMLElement interface represents the rendered text content of a node and its descendants.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/innerText)

The **`lang`** property of the HTMLElement interface indicates the base language of an element's attribute values and text content, in the form of a BCP 47 language tag. It reflects the element's lang attribute; the xml:lang attribute does not affect this property.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/lang)

The **`offsetHeight`** read-only property of the HTMLElement interface returns the height of an element, including vertical padding and borders, as an integer.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/offsetHeight)

The **`offsetLeft`** read-only property of the HTMLElement interface returns the number of pixels that the upper left corner of the current element is offset to the left within the HTMLElement.offsetParent node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/offsetLeft)

The **`HTMLElement.offsetParent`** read-only property returns a reference to the element which is the closest (nearest in the containment hierarchy) positioned ancestor element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/offsetParent)

The **`offsetTop`** read-only property of the HTMLElement interface returns the distance from the outer border of the current element (including its margin) to the top padding edge of the offsetParent, the closest positioned ancestor element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/offsetTop)

The **`offsetWidth`** read-only property of the HTMLElement interface returns the layout width of an element as an integer.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/offsetWidth)

The **`outerText`** property of the HTMLElement interface returns the same value as HTMLElement.innerText. When used as a setter it replaces the whole current node with the given text (this differs from innerText, which replaces the content inside the current node).

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/outerText)

The **`popover`** property of the HTMLElement interface gets and sets an element's popover state via JavaScript ("auto", "hint", or "manual"), and can be used for feature detection.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/popover)

The **`spellcheck`** property of the HTMLElement interface represents a boolean value that controls the spell-checking hint. It is available on all HTML elements, though it doesn't affect all of them.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/spellcheck)

The **`HTMLElement.title`** property represents the title of the element: the text usually displayed in a 'tooltip' popup when the mouse is over the node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/title)

The **`translate`** property of the HTMLElement interface indicates whether an element's attribute values and the values of its Text node children are to be translated when the page is localized, or whether to leave them unchanged.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/translate)

The **`writingSuggestions`** property of the HTMLElement interface is a string indicating if browser-provided writing suggestions should be enabled under the scope of the element or not.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/writingSuggestions)

The **`HTMLElement.attachInternals()`** method returns an ElementInternals object. This method allows a custom element to participate in HTML forms. The ElementInternals interface provides utilities for working with these elements in the same way you would work with any standard HTML form element, and also exposes the Accessibility Object Model to the element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/attachInternals)

@returns`ElementInternals`

The **`HTMLElement.click()`** method simulates a mouse click on an element. When called on an element, the element's click event is fired (unless its disabled attribute is set).

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/click)

@returns`void`

The **`hidePopover()`** method of the HTMLElement interface hides a popover element (i.e., one that has a valid popover attribute) by removing it from the top layer and styling it with display: none.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/hidePopover)

@returns`void`

The **`showPopover()`** method of the HTMLElement interface shows a popover element (i.e., one that has a valid popover attribute) by adding it to the top layer.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/showPopover)

@paramoptions`ShowPopoverOptions | undefined`

@returns`void`

The **`togglePopover()`** method of the HTMLElement interface toggles a popover element (i.e., one that has a valid popover attribute) between the hidden and showing states.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/togglePopover)

@paramoptions`boolean | TogglePopoverOptions | undefined`

@returns`boolean`

@paramtype`K`

@paramlistener`(this: HTMLElement, ev: HTMLElementEventMap[K]) => any`

@paramoptions`boolean | AddEventListenerOptions | undefined`

@returns`void`

@paramtype`string`

@paramlistener`EventListenerOrEventListenerObject`

@paramoptions`boolean | AddEventListenerOptions | undefined`

@returns`void`

@paramtype`K`

@paramlistener`(this: HTMLElement, ev: HTMLElementEventMap[K]) => any`

@paramoptions`boolean | EventListenerOptions | undefined`

@returns`void`

@paramtype`string`

@paramlistener`EventListenerOrEventListenerObject`

@paramoptions`boolean | EventListenerOptions | undefined`

@returns`void`

The **`Element.attributes`** property returns a live collection of all attribute nodes registered to the specified node. It is a NamedNodeMap, not an Array, so it has no Array methods and the Attr nodes' indexes may differ among browsers. To be more specific, attributes is a key/value pair of strings that represents any information regarding that attribute.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/attributes)

The read-only **`classList`** property of the Element interface contains a live DOMTokenList collection representing the class attribute of the element. This can then be used to manipulate the class list.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/classList)

The **`className`** property of the Element interface gets and sets the value of the class attribute of the specified element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/className)

The **`clientHeight`** read-only property of the Element interface is zero for elements with no CSS or inline layout boxes; otherwise, it's the inner height of an element in pixels. It includes padding but excludes borders, margins, and horizontal scrollbars (if present).

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/clientHeight)

The **`clientLeft`** read-only property of the Element interface returns the width of the left border of an element in pixels. It includes the width of the vertical scrollbar if the text direction of the element is right-to-left and if there is an overflow causing a left vertical scrollbar to be rendered. clientLeft does not include the left margin or the left padding.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/clientLeft)

The **`clientTop`** read-only property of the Element interface returns the width of the top border of an element in pixels.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/clientTop)

The **`clientWidth`** read-only property of the Element interface is zero for inline elements and elements with no CSS; otherwise, it's the inner width of an element in pixels. It includes padding but excludes borders, margins, and vertical scrollbars (if present).

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/clientWidth)

The **`currentCSSZoom`** read-only property of the Element interface provides the "effective" CSS zoom of an element, taking into account the zoom applied to the element and all its parent elements.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/currentCSSZoom)

The **`id`** property of the Element interface represents the element's identifier, reflecting the id global attribute.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/id)

The **`innerHTML`** property of the Element interface gets or sets the HTML or XML markup contained within the element, omitting any shadow roots in both cases.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/innerHTML)

The **`Element.localName`** read-only property returns the local part of the qualified name of an element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/localName)

The **`Element.namespaceURI`** read-only property returns the namespace URI of the element, or null if the element is not in a namespace.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/namespaceURI)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/fullscreenchange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/fullscreenerror_event)

The **`outerHTML`** attribute of the Element interface gets or sets the HTML or XML markup of the element and its descendants, omitting any shadow roots in both cases.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/outerHTML)

The read-only **`part`** property of the Element interface contains a DOMTokenList object representing the part identifier(s) of the element. It reflects the element's part content attribute. These can be used to style parts of a shadow DOM, via the ::part pseudo-element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/part)

The **`Element.prefix`** read-only property returns the namespace prefix of the specified element, or null if no prefix is specified.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/prefix)

The **`scrollHeight`** read-only property of the Element interface is a measurement of the height of an element's content, including content not visible on the screen due to overflow.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollHeight)

The **`scrollLeft`** property of the Element interface gets or sets the number of pixels by which an element's content is scrolled from its left edge. This value is subpixel precise in modern browsers, meaning that it isn't necessarily a whole number.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollLeft)

The **`scrollTop`** property of the Element interface gets or sets the number of pixels by which an element's content is scrolled from its top edge. This value is subpixel precise in modern browsers, meaning that it isn't necessarily a whole number.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollTop)

The **`scrollWidth`** read-only property of the Element interface is a measurement of the width of an element's content, including content not visible on the screen due to overflow.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollWidth)

The **`Element.shadowRoot`** read-only property represents the shadow root hosted by the element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/shadowRoot)

The **`slot`** property of the Element interface returns the name of the shadow DOM slot the element is inserted in.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/slot)

The **`tagName`** read-only property of the Element interface returns the tag name of the element on which it's called.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/tagName)

The **`Element.attachShadow()`** method attaches a shadow DOM tree to the specified element and returns a reference to its ShadowRoot.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/attachShadow)

@paraminit`ShadowRootInit`

@returns`ShadowRoot`

The **`checkVisibility()`** method of the Element interface checks whether the element is visible.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/checkVisibility)

@paramoptions`CheckVisibilityOptions | undefined`

@returns`boolean`

The **`closest()`** method of the Element interface traverses the element and its parents (heading toward the document root) until it finds a node that matches the specified CSS selector.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/closest)

@paramselector`K`

@returns`HTMLElementTagNameMap[K] | null`

@paramselector`K`

@returns`SVGElementTagNameMap[K] | null`

@paramselector`K`

@returns`MathMLElementTagNameMap[K] | null`

@paramselectors`string`

@returns`E | null`

The **`computedStyleMap()`** method of the Element interface returns a StylePropertyMapReadOnly interface which provides a read-only representation of a CSS declaration block that is an alternative to CSSStyleDeclaration.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/computedStyleMap)

@returns`StylePropertyMapReadOnly`

The **`getAttribute()`** method of the Element interface returns the value of a specified attribute on the element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttribute)

@paramqualifiedName`string`

@returns`string | null`

The **`getAttributeNS()`** method of the Element interface returns the string value of the attribute with the specified namespace and name. If the named attribute does not exist, the value returned will either be null or "" (the empty string); see Notes for details.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNS)

@paramnamespace`string | null`

@paramlocalName`string`

@returns`string | null`

The **`getAttributeNames()`** method of the Element interface returns the attribute names of the element as an Array of strings. If the element has no attributes it returns an empty array.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNames)

@returns`string[]`

Returns the specified attribute of the specified element, as an Attr node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNode)

@paramqualifiedName`string`

@returns`Attr | null`

The **`getAttributeNodeNS()`** method of the Element interface returns the namespaced Attr node of an element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAttributeNodeNS)

@paramnamespace`string | null`

@paramlocalName`string`

@returns`Attr | null`

The **`Element.getBoundingClientRect()`** method returns a DOMRect object providing information about the size of an element and its position relative to the viewport.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getBoundingClientRect)

@returns`DOMRect`

The **`getClientRects()`** method of the Element interface returns a collection of DOMRect objects that indicate the bounding rectangles for each CSS border box in a client.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getClientRects)

@returns`DOMRectList`

The Element method **`getElementsByClassName()`** returns a live HTMLCollection which contains every descendant element which has the specified class name or names.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getElementsByClassName)

@paramclassNames`string`

@returns`HTMLCollectionOf<Element>`

The **`Element.getElementsByTagName()`** method returns a live HTMLCollection of elements with the given tag name.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getElementsByTagName)

@paramqualifiedName`K`

@returns`HTMLCollectionOf<HTMLElementTagNameMap[K]>`

@paramqualifiedName`K`

@returns`HTMLCollectionOf<SVGElementTagNameMap[K]>`

@paramqualifiedName`K`

@returns`HTMLCollectionOf<MathMLElementTagNameMap[K]>`

@deprecated

@paramqualifiedName`K`

@returns`HTMLCollectionOf<HTMLElementDeprecatedTagNameMap[K]>`

@paramqualifiedName`string`

@returns`HTMLCollectionOf<Element>`

The **`Element.getElementsByTagNameNS()`** method returns a live HTMLCollection of elements with the given tag name belonging to the given namespace. It is similar to Document.getElementsByTagNameNS, except that its search is restricted to descendants of the specified element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getElementsByTagNameNS)

@paramnamespaceURI`"http://www.w3.org/1999/xhtml"`

@paramlocalName`string`

@returns`HTMLCollectionOf<HTMLElement>`

@paramnamespaceURI`"http://www.w3.org/2000/svg"`

@paramlocalName`string`

@returns`HTMLCollectionOf<SVGElement>`

@paramnamespaceURI`"http://www.w3.org/1998/Math/MathML"`

@paramlocalName`string`

@returns`HTMLCollectionOf<MathMLElement>`

@paramnamespace`string | null`

@paramlocalName`string`

@returns`HTMLCollectionOf<Element>`

The **`getHTML()`** method of the Element interface is used to serialize an element's DOM to an HTML string.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getHTML)

@paramoptions`GetHTMLOptions | undefined`

@returns`string`

The **`Element.hasAttribute()`** method returns a Boolean value indicating whether the specified element has the specified attribute or not.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasAttribute)

@paramqualifiedName`string`

@returns`boolean`

The **`hasAttributeNS()`** method of the Element interface returns a boolean value indicating whether the current element has the specified attribute with the specified namespace.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasAttributeNS)

@paramnamespace`string | null`

@paramlocalName`string`

@returns`boolean`

The **`hasAttributes()`** method of the Element interface returns a boolean value indicating whether the current element has any attributes or not.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasAttributes)

@returns`boolean`

The **`hasPointerCapture()`** method of the Element interface checks whether the element on which it is invoked has pointer capture for the pointer identified by the given pointer ID.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/hasPointerCapture)

@parampointerId`number`

@returns`boolean`

The **`insertAdjacentElement()`** method of the Element interface inserts a given element node at a given position relative to the element it is invoked upon.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/insertAdjacentElement)

@paramwhere`InsertPosition`

@paramelement`Element`

@returns`Element | null`

The **`insertAdjacentHTML()`** method of the Element interface parses the specified input as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/insertAdjacentHTML)

@paramposition`InsertPosition`

@paramstring`string`

@returns`void`

The **`insertAdjacentText()`** method of the Element interface, given a relative position and a string, inserts a new text node at the given position relative to the element it is called from.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/insertAdjacentText)

@paramwhere`InsertPosition`

@paramdata`string`

@returns`void`

The **`matches()`** method of the Element interface tests whether the element would be selected by the specified CSS selector.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/matches)

@paramselectors`K`

@returns`this is HTMLElementTagNameMap[K]`

@paramselectors`K`

@returns`this is SVGElementTagNameMap[K]`

@paramselectors`K`

@returns`this is MathMLElementTagNameMap[K]`

@paramselectors`string`

@returns`boolean`

The **`releasePointerCapture()`** method of the Element interface releases (stops) pointer capture that was previously set for a specific (PointerEvent) pointer.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture)

@parampointerId`number`

@returns`void`

The Element method **`removeAttribute()`** removes the attribute with the specified name from the element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/removeAttribute)

@paramqualifiedName`string`

@returns`void`

The **`removeAttributeNS()`** method of the Element interface removes the specified attribute with the specified namespace from an element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/removeAttributeNS)

@paramnamespace`string | null`

@paramlocalName`string`

@returns`void`

The **`removeAttributeNode()`** method of the Element interface removes the specified Attr node from the element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/removeAttributeNode)

@paramattr`Attr`

@returns`Attr`

The **`Element.requestFullscreen()`** method issues an asynchronous request to make the element be displayed in fullscreen mode.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/requestFullscreen)

@paramoptions`FullscreenOptions | undefined`

@returns`Promise<void>`

The **`requestPointerLock()`** method of the Element interface lets you asynchronously ask for the pointer to be locked on the given element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/requestPointerLock)

@paramoptions`PointerLockOptions | undefined`

@returns`Promise<void>`

The **`scroll()`** method of the Element interface scrolls the element to a particular set of coordinates inside a given element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scroll)

@paramoptions`ScrollToOptions | undefined`

@returns`void`

@paramx`number`

@paramy`number`

@returns`void`

The **`scrollBy()`** method of the Element interface scrolls an element by the given amount.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollBy)

@paramoptions`ScrollToOptions | undefined`

@returns`void`

@paramx`number`

@paramy`number`

@returns`void`

The Element interface's **`scrollIntoView()`** method scrolls the element's ancestor containers such that the element on which scrollIntoView() is called is visible to the user.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollIntoView)

@paramarg`boolean | ScrollIntoViewOptions | undefined`

@returns`void`

The **`scrollTo()`** method of the Element interface scrolls to a particular set of coordinates inside a given element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/scrollTo)

@paramoptions`ScrollToOptions | undefined`

@returns`void`

@paramx`number`

@paramy`number`

@returns`void`

The **`setAttribute()`** method of the Element interface sets the value of an attribute on the specified element. If the attribute already exists, the value is updated; otherwise a new attribute is added with the specified name and value.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttribute)

@paramqualifiedName`string`

@paramvalue`string`

@returns`void`

The **`setAttributeNS()`** method of the Element interface adds a new attribute or changes the value of an attribute with the given namespace and name.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttributeNS)

@paramnamespace`string | null`

@paramqualifiedName`string`

@paramvalue`string`

@returns`void`

The **`setAttributeNode()`** method of the Element interface adds a new Attr node to the specified element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttributeNode)

@paramattr`Attr`

@returns`Attr | null`

The **`setAttributeNodeNS()`** method of the Element interface adds a new namespaced Attr node to an element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setAttributeNodeNS)

@paramattr`Attr`

@returns`Attr | null`

The **`setHTMLUnsafe()`** method of the Element interface is used to parse HTML input into a DocumentFragment, optionally filtering out unwanted elements and attributes, and those that don't belong in the context, and then using it to replace the element's subtree in the DOM.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setHTMLUnsafe)

@paramhtml`string`

@returns`void`

The **`setPointerCapture()`** method of the Element interface is used to designate a specific element as the capture target of future pointer events. Subsequent events for the pointer will be targeted at the capture element until capture is released (via Element.releasePointerCapture() or the pointerup event is fired).

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/setPointerCapture)

@parampointerId`number`

@returns`void`

The **`toggleAttribute()`** method of the Element interface toggles a Boolean attribute (removing it if it is present and adding it if it is not present) on the given element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/toggleAttribute)

@paramqualifiedName`string`

@paramforce`boolean | undefined`

@returns`boolean`

@deprecated

This is a legacy alias of `matches`.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/matches)

@paramselectors`string`

@returns`boolean`

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/textContent)

The read-only **`baseURI`** property of the Node interface returns the absolute base URL of the document containing the node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/baseURI)

The read-only **`childNodes`** property of the Node interface returns a live NodeList of child nodes of the given element where the first child node is assigned index 0. Child nodes include elements, text and comments.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/childNodes)

The read-only **`firstChild`** property of the Node interface returns the node's first child in the tree, or null if the node has no children.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/firstChild)

The read-only **`isConnected`** property of the Node interface returns a boolean indicating whether the node is connected (directly or indirectly) to a Document object.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/isConnected)

The read-only **`lastChild`** property of the Node interface returns the last child of the node, or null if there are no child nodes.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/lastChild)

The read-only **`nextSibling`** property of the Node interface returns the node immediately following the specified one in their parent's childNodes, or returns null if the specified node is the last child in the parent element.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nextSibling)

The read-only **`nodeName`** property of Node returns the name of the current node as a string.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nodeName)

The read-only **`nodeType`** property of a Node interface is an integer that identifies what the node is. It distinguishes different kinds of nodes from each other, such as elements, text, and comments.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nodeType)

The **`nodeValue`** property of the Node interface returns or sets the value of the current node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/nodeValue)

The read-only **`parentElement`** property of Node interface returns the DOM node's parent Element, or null if the node either has no parent, or its parent isn't a DOM Element. Node.parentNode on the other hand returns any kind of parent, regardless of its type.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/parentElement)

The read-only **`parentNode`** property of the Node interface returns the parent of the specified node in the DOM tree.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/parentNode)

The read-only **`previousSibling`** property of the Node interface returns the node immediately preceding the specified one in its parent's childNodes list, or null if the specified node is the first in that list.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/previousSibling)

The **`appendChild()`** method of the Node interface adds a node to the end of the list of children of a specified parent node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/appendChild)

@paramnode`T`

@returns`T`

The **`cloneNode()`** method of the Node interface returns a duplicate of the node on which this method was called. Its parameter controls if the subtree contained in the node is also cloned or not.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/cloneNode)

@paramsubtree`boolean | undefined`

@returns`Node`

The **`compareDocumentPosition()`** method of the Node interface reports the position of its argument node relative to the node on which it is called.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/compareDocumentPosition)

@paramother`Node`

@returns`number`

The **`contains()`** method of the Node interface returns a boolean value indicating whether a node is a descendant of a given node, that is the node itself, one of its direct children (childNodes), one of the children's direct children, and so on.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/contains)

@paramother`Node | null`

@returns`boolean`

The **`getRootNode()`** method of the Node interface returns the context object's root, which optionally includes the shadow root if it is available.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/getRootNode)

@paramoptions`GetRootNodeOptions | undefined`

@returns`Node`

The **`hasChildNodes()`** method of the Node interface returns a boolean value indicating whether the given Node has child nodes or not.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/hasChildNodes)

@returns`boolean`

The **`insertBefore()`** method of the Node interface inserts a node before a reference node as a child of a specified parent node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/insertBefore)

@paramnode`T`

@paramchild`Node | null`

@returns`T`

The **`isDefaultNamespace()`** method of the Node interface accepts a namespace URI as an argument. It returns a boolean value that is true if the namespace is the default namespace on the given node and false if not. The default namespace can be retrieved with Node.lookupNamespaceURI() by passing null as the argument.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/isDefaultNamespace)

@paramnamespace`string | null`

@returns`boolean`

The **`isEqualNode()`** method of the Node interface tests whether two nodes are equal. Two nodes are equal when they have the same type, defining characteristics (for elements, this would be their ID, number of children, and so forth), its attributes match, and so on. The specific set of data points that must match varies depending on the types of the nodes.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/isEqualNode)

@paramotherNode`Node | null`

@returns`boolean`

The **`isSameNode()`** method of the Node interface is a legacy alias the for the === strict equality operator. That is, it tests whether two nodes are the same (in other words, whether they reference the same object).

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/isSameNode)

@paramotherNode`Node | null`

@returns`boolean`

The **`lookupNamespaceURI()`** method of the Node interface takes a prefix as parameter and returns the namespace URI associated with it on the given node if found (and null if not). This method's existence allows Node objects to be passed as a namespace resolver to XPathEvaluator.createExpression() and XPathEvaluator.evaluate().

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/lookupNamespaceURI)

@paramprefix`string | null`

@returns`string | null`

The **`lookupPrefix()`** method of the Node interface returns a string containing the prefix for a given namespace URI, if present, and null if not. When multiple prefixes are possible, the first prefix is returned.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/lookupPrefix)

@paramnamespace`string | null`

@returns`string | null`

The **`normalize()`** method of the Node interface puts the specified node and all of its sub-tree into a normalized form. In a normalized sub-tree, no text nodes in the sub-tree are empty and there are no adjacent text nodes.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/normalize)

@returns`void`

The **`removeChild()`** method of the Node interface removes a child node from the DOM and returns the removed node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/removeChild)

@paramchild`T`

@returns`T`

The **`replaceChild()`** method of the Node interface replaces a child node within the given (parent) node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/replaceChild)

@paramnode`Node`

@paramchild`T`

@returns`T`

node is an element.

node is a Text node.

node is a CDATASection node.

node is a ProcessingInstruction node.

node is a Comment node.

node is a document.

node is a doctype.

node is a DocumentFragment node.

Set when node and other are not in the same tree.

Set when other is preceding node.

Set when other is following node.

Set when other is an ancestor of node.

Set when other is a descendant of node.

The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. The normal event processing rules (including the capturing and optional bubbling phase) also apply to events dispatched manually with dispatchEvent().

[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent)

@paramevent`Event`

@returns`boolean`

Remove all event listeners by name for this event target.

This method is optional because it may not be available if you use `noop zone` when bootstrapping Angular application or disable the `EventTarget` monkey patch by `zone.js`.

If the `eventName` is provided, will remove event listeners of that name. If the `eventName` is not provided, will remove all event listeners associated with `EventTarget`.

@parameventName`string | undefined`

the name of the event, such as `click`. This parameter is optional.

@returns`void`

Retrieve all event listeners by name.

This method is optional because it may not be available if you use `noop zone` when bootstrapping Angular application or disable the `EventTarget` monkey patch by `zone.js`.

If the `eventName` is provided, will return an array of event handlers or event listener objects of the given event. If the `eventName` is not provided, will return all listeners.

@parameventName`string | undefined`

the name of the event, such as click. This parameter is optional.

@returns`EventListenerOrEventListenerObject[]`

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaActiveDescendantElement)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaAtomic)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaAutoComplete)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaBrailleLabel)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaBrailleRoleDescription)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaBusy)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaChecked)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaColCount)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaColIndex)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaColIndexText)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaColSpan)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaControlsElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaCurrent)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaDescribedByElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaDescription)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaDetailsElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaDisabled)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaErrorMessageElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaExpanded)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaFlowToElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaHasPopup)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaHidden)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaInvalid)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaKeyShortcuts)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaLabel)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaLabelledByElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaLevel)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaLive)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaModal)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaMultiLine)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaMultiSelectable)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaOrientation)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaOwnsElements)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaPlaceholder)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaPosInSet)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaPressed)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaReadOnly)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRelevant)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRequired)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRoleDescription)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRowCount)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRowIndex)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRowIndexText)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaRowSpan)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaSelected)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaSetSize)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaSort)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaValueMax)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaValueMin)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaValueNow)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/ariaValueText)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/role)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animate)

@paramkeyframes`Keyframe[] | PropertyIndexedKeyframes | null`

@paramoptions`number | KeyframeAnimationOptions | undefined`

@returns`Animation`

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getAnimations)

@paramoptions`GetAnimationsOptions | undefined`

@returns`Animation[]`

Inserts nodes just after node, while replacing strings in nodes with equivalent Text nodes.

Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/after)

@paramnodes`(string | Node)[]`

@returns`void`

Inserts nodes just before node, while replacing strings in nodes with equivalent Text nodes.

Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/before)

@paramnodes`(string | Node)[]`

@returns`void`

Removes node.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/remove)

@returns`void`

Replaces node with nodes, while replacing strings in nodes with equivalent Text nodes.

Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/replaceWith)

@paramnodes`(string | Node)[]`

@returns`void`

Returns the first following sibling that is an element, and null otherwise.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/nextElementSibling)

Returns the first preceding sibling that is an element, and null otherwise.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/previousElementSibling)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/childElementCount)

Returns the child elements.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/children)

Returns the first child that is an element, and null otherwise.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/firstElementChild)

Returns the last child that is an element, and null otherwise.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/lastElementChild)

Inserts nodes after the last child of node, while replacing strings in nodes with equivalent Text nodes.

Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/append)

@paramnodes`(string | Node)[]`

@returns`void`

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/moveBefore)

@paramnode`Node`

@paramchild`Node | null`

@returns`void`

Inserts nodes before the first child of node, while replacing strings in nodes with equivalent Text nodes.

Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/prepend)

@paramnodes`(string | Node)[]`

@returns`void`

Returns the first element that is a descendant of node that matches selectors.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelector)

@paramselectors`K`

@returns`HTMLElementTagNameMap[K] | null`

@paramselectors`K`

@returns`SVGElementTagNameMap[K] | null`

@paramselectors`K`

@returns`MathMLElementTagNameMap[K] | null`

@deprecated

@paramselectors`K`

@returns`HTMLElementDeprecatedTagNameMap[K] | null`

@paramselectors`string`

@returns`E | null`

Returns all element descendants of node that match selectors.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll)

@paramselectors`K`

@returns`NodeListOf<HTMLElementTagNameMap[K]>`

@paramselectors`K`

@returns`NodeListOf<SVGElementTagNameMap[K]>`

@paramselectors`K`

@returns`NodeListOf<MathMLElementTagNameMap[K]>`

@deprecated

@paramselectors`K`

@returns`NodeListOf<HTMLElementDeprecatedTagNameMap[K]>`

@paramselectors`string`

@returns`NodeListOf<E>`

Replace all children of node with nodes, while replacing strings in nodes with equivalent Text nodes.

Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/replaceChildren)

@paramnodes`(string | Node)[]`

@returns`void`

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/assignedSlot)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/attributeStyleMap)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/style)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/contentEditable)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/enterKeyHint)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/inputMode)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/isContentEditable)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/abort_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationcancel_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationiteration_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationstart_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/auxclick_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/beforeinput_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/beforematch_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/beforetoggle_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/blur_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLDialogElement/cancel_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/canplay_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/canplaythrough_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/change_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/click_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLDialogElement/close_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/command_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/contextlost_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/contextmenu_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/contextrestored_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/copy_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLTrackElement/cuechange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/cut_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/dblclick_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/drag_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dragend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dragenter_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dragleave_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dragover_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dragstart_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/drop_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/durationchange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/emptied_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/ended_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/error_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/focus_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLFormElement/formdata_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/gotpointercapture_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/input_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/invalid_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/keydown_event)

@deprecated

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/keypress_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/keyup_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/load_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/loadeddata_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/loadedmetadata_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/loadstart_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/lostpointercapture_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mousedown_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mouseenter_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mouseleave_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mousemove_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mouseout_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mouseover_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/mouseup_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/paste_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/pause_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/play_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/playing_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointercancel_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerdown_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerenter_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerleave_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointermove_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerout_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerover_event)

Available only in secure contexts.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerrawupdate_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/pointerup_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/progress_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/ratechange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLFormElement/reset_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement/resize_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/scroll_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/scrollend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/securitypolicyviolation_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/seeked_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/seeking_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/select_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/selectionchange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/selectstart_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLSlotElement/slotchange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/stalled_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLFormElement/submit_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/suspend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/timeupdate_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/toggle_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/touchcancel_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/touchend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/touchmove_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/touchstart_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/transitioncancel_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/transitionend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/transitionrun_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/transitionstart_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/volumechange_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/waiting_event)

@deprecated

This is a legacy alias of `onanimationend`.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationend_event)

@deprecated

This is a legacy alias of `onanimationiteration`.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationiteration_event)

@deprecated

This is a legacy alias of `onanimationstart`.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/animationstart_event)

@deprecated

This is a legacy alias of `ontransitionend`.

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/transitionend_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/wheel_event)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/autofocus)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dataset)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/nonce)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/tabIndex)

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/blur)

@returns`void`

[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/focus)

@paramoptions`FocusOptions | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/withViewTransitions
----

```
function withViewTransitions(
  options?: ViewTransitionsFeatureOptions | undefined,
): ViewTransitionsFeature;
```

@paramoptions`ViewTransitionsFeatureOptions | undefined`

@returns`ViewTransitionsFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Description](#description)

Enables view transitions in the Router by running the route activation and deactivation inside of `document.startViewTransition`.

Note: The View Transitions API is not available in all browsers. If the browser does not support view transitions, the Router will not attempt to start a view transition and continue processing the navigation as usual.

## [Usage Notes](#usage-notes)

Basic example of how you can enable the feature:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withViewTransitions())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/guide/testing/creating-component-harnesses
----

### [When does creating a test harness make sense?](#when-does-creating-a-test-harness-make-sense)

The Angular team recommends creating component test harnesses for shared components that are used in many places and have some user interactivity. This most commonly applies to widget libraries and similar reusable components. Harnesses are valuable for these cases because they provide the consumers of these shared components a well- supported API for interacting with a component. Tests that use harnesses can avoid depending on unreliable implementation details of these shared components, such as DOM structure and specific event listeners.

For components that appear in only one place, such as a page in an application, harnesses don't provide as much benefit. In these situations, a component's tests can reasonably depend on the implementation details of this component, as the tests and components are updated at the same time. However, harnesses still provide some value if you would use the harness in both unit and end-to-end tests.

### [CDK Installation](#cdk-installation)

The [Component Dev Kit (CDK)](https://material.angular.dev/cdk/categories) is a set of behavior primitives for building components. To use the component harnesses, first install `@angular/cdk` from npm. You can do this from your terminal using the Angular CLI:

```
ng add @angular/cdk
```

## [Extending `ComponentHarness`](#extending-componentharness)

The abstract [`ComponentHarness`](/api/cdk/testing/ComponentHarness) class is the base class for all component harnesses. To create a custom component harness, extend [`ComponentHarness`](/api/cdk/testing/ComponentHarness) and implement the static property `hostSelector`.

The `hostSelector` property identifies elements in the DOM that match this harness subclass. In most cases, the `hostSelector` should be the same as the selector of the corresponding [`Component`](/api/core/Component) or [`Directive`](/api/core/Directive). For example, consider a simple popup component:

```
@Component({
  selector: 'my-popup',
  template: `
    <button (click)="toggle()">{{ triggerText() }}</button>
    @if (isOpen()) {
      <div class="my-popup-content"><ng-content /></div>
    }
  `,
})
class MyPopup {
  triggerText = input('');

  isOpen = signal(false);

  toggle() {
    this.isOpen.update((value) => !value);
  }
}
```

In this case, a minimal harness for the component would look like the following:

```
class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';
}
```

While [`ComponentHarness`](/api/cdk/testing/ComponentHarness) subclasses require only the `hostSelector` property, most harnesses should also implement a static `with` method to generate [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) instances. The [filtering harnesses section](guide/testing/using-component-harnesses#filtering-harnesses) covers this in more detail.

## [Finding elements in the component's DOM](#finding-elements-in-the-components-dom)

Each instance of a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) subclass represents a particular instance of the corresponding component. You can access the component's host element via the `host() `method from the [`ComponentHarness`](/api/cdk/testing/ComponentHarness) base class.

[`ComponentHarness`](/api/cdk/testing/ComponentHarness) also offers several methods for locating elements within the component's DOM. These methods are `locatorFor()`, `locatorForOptional()`, and `locatorForAll()`. These methods create functions that find elements, they do not directly find elements. This approach safeguards against caching references to out-of-date elements. For example, when an `@if` block hides and then shows an element, the result is a new DOM element; using functions ensures that tests always reference the current state of the DOM.

See the [ComponentHarness API reference page](/api/cdk/testing/ComponentHarness) for the full list details of the different `locatorFor` methods.

For example, the `MyPopupHarness` example discussed above could provide methods to get the trigger and content elements as follows:

```
class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';

  // Gets the trigger element
  getTriggerElement = this.locatorFor('button');

  // Gets the content element.
  getContentElement = this.locatorForOptional('.my-popup-content');
}
```

## [Working with `TestElement` instances](#working-with-testelement-instances)

[`TestElement`](/api/cdk/testing/TestElement) is an abstraction designed to work across different test environments (Unit tests, WebDriver, etc). When using harnesses, you should perform all DOM interaction via this interface. Other means of accessing DOM elements, such as `document.querySelector()`, do not work in all test environments.

[`TestElement`](/api/cdk/testing/TestElement) has a number of methods to interact with the underlying DOM, such as `blur()`, `click()`, `getAttribute()`, and more. See the [TestElement API reference page](/api/cdk/testing/TestElement) for the full list of methods.

Do not expose [`TestElement`](/api/cdk/testing/TestElement) instances to harness users unless it's an element the component consumer defines directly, such as the component's host element. Exposing [`TestElement`](/api/cdk/testing/TestElement) instances for internal elements leads users to depend on a component's internal DOM structure.

Instead, provide more narrow-focused methods for specific actions the end-user may take or particular state they may observe. For example, `MyPopupHarness` from previous sections could provide methods like `toggle` and `isOpen`:

```
class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';

  protected getTriggerElement = this.locatorFor('button');
  protected getContentElement = this.locatorForOptional('.my-popup-content');

  /** Toggles the open state of the popup. */
  async toggle() {
    const trigger = await this.getTriggerElement();
    return trigger.click();
  }

  /** Checks if the popup us open. */
  async isOpen() {
    const content = await this.getContentElement();
    return !!content;
  }
}
```

## [Loading harnesses for subcomponents](#loading-harnesses-for-subcomponents)

Larger components often compose sub-components. You can reflect this structure in a component's harness as well. Each of the `locatorFor` methods on [`ComponentHarness`](/api/cdk/testing/ComponentHarness) has an alternate signature that can be used for locating sub-harnesses rather than elements.

See the [ComponentHarness API reference page](/api/cdk/testing/ComponentHarness) for the full list of the different locatorFor methods.

For example, consider a menu build using the popup from above:

```
@Directive({
  selector: 'my-menu-item',
})
class MyMenuItem {}

@Component({
  selector: 'my-menu',
  template: `
    <my-popup>
      <ng-content />
    </my-popup>
  `,
})
class MyMenu {
  triggerText = input('');

  items = contentChildren(MyMenuItem);
}
```

The harness for `MyMenu` can then take advantage of other harnesses for `MyPopup` and `MyMenuItem`:

```
class MyMenuHarness extends ComponentHarness {
  static hostSelector = 'my-menu';

  protected getPopupHarness = this.locatorFor(MyPopupHarness);

  /** Gets the currently shown menu items (empty list if menu is closed). */
  getItems = this.locatorForAll(MyMenuItemHarness);

  /** Toggles open state of the menu. */
  async toggle() {
    const popupHarness = await this.getPopupHarness();
    return popupHarness.toggle();
  }
}

class MyMenuItemHarness extends ComponentHarness {
  static hostSelector = 'my-menu-item';
}
```

## [Filtering harness instances with `HarnessPredicate`](#filtering-harness-instances-with-harnesspredicate)

When a page contains multiple instances of a particular component, you may want to filter based on some property of the component to get a particular component instance. For example, you may want a button with some specific text, or a menu with a specific ID. The [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) class can capture criteria like this for a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) subclass. While the test author is able to construct [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) instances manually, it's easier when the [`ComponentHarness`](/api/cdk/testing/ComponentHarness) subclass provides a helper method to construct predicates for common filters.

You should create a static `with()` method on each [`ComponentHarness`](/api/cdk/testing/ComponentHarness) subclass that returns a [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) for that class. This allows test authors to write easily understandable code, e.g. `loader.getHarness(MyMenuHarness.with({selector: '#menu1'}))`. In addition to the standard selector and ancestor options, the `with` method should add any other options that make sense for the particular subclass.

Harnesses that need to add additional options should extend the [`BaseHarnessFilters`](/api/cdk/testing/BaseHarnessFilters) interface and additional optional properties as needed. [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) provides several convenience methods for adding options: `stringMatches()`, `addOption()`, and `add()`. See the [HarnessPredicate API page](/api/cdk/testing/HarnessPredicate) for the full description.

For example, when working with a menu it is useful to filter based on trigger text and to filter menu items based on their text:

```
interface MyMenuHarnessFilters extends BaseHarnessFilters {
  /** Filters based on the trigger text for the menu. */
  triggerText?: string | RegExp;
}

interface MyMenuItemHarnessFilters extends BaseHarnessFilters {
  /** Filters based on the text of the menu item. */
  text?: string | RegExp;
}

class MyMenuHarness extends ComponentHarness {
  static hostSelector = 'my-menu';

  /** Creates a `HarnessPredicate` used to locate a particular `MyMenuHarness`. */
  static with(options: MyMenuHarnessFilters): HarnessPredicate<MyMenuHarness> {
    return new HarnessPredicate(MyMenuHarness, options).addOption(
      'trigger text',
      options.triggerText,
      (harness, text) => HarnessPredicate.stringMatches(harness.getTriggerText(), text),
    );
  }

  protected getPopupHarness = this.locatorFor(MyPopupHarness);

  /** Gets the text of the menu trigger. */
  async getTriggerText(): Promise<string> {
    const popupHarness = await this.getPopupHarness();
    return popupHarness.getTriggerText();
  }
}

class MyMenuItemHarness extends ComponentHarness {
  static hostSelector = 'my-menu-item';

  /** Creates a `HarnessPredicate` used to locate a particular `MyMenuItemHarness`. */
  static with(options: MyMenuItemHarnessFilters): HarnessPredicate<MyMenuItemHarness> {
    return new HarnessPredicate(MyMenuItemHarness, options).addOption(
      'text',
      options.text,
      (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text),
    );
  }

  /** Gets the text of the menu item. */
  async getText(): Promise<string> {
    const host = await this.host();
    return host.text();
  }
}
```

You can pass a [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) instead of a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) class to any of the APIs on [`HarnessLoader`](/api/cdk/testing/HarnessLoader), [`LocatorFactory`](/api/cdk/testing/LocatorFactory), or [`ComponentHarness`](/api/cdk/testing/ComponentHarness). This allows test authors to easily target a particular component instance when creating a harness instance. It also allows the harness author to leverage the same [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) to enable more powerful APIs on their harness class. For example, consider the `getItems` method on the `MyMenuHarness` shown above. Adding a filtering API allows users of the harness to search for particular menu items:

```
class MyMenuHarness extends ComponentHarness {
  static hostSelector = 'my-menu';

  /** Gets a list of items in the menu, optionally filtered based on the given criteria. */
  async getItems(filters: MyMenuItemHarnessFilters = {}): Promise<MyMenuItemHarness[]> {
    const getFilteredItems = this.locatorForAll(MyMenuItemHarness.with(filters));
    return getFilteredItems();
  }
  ...
}
```

## [Creating `HarnessLoader` for elements that use content projection](#creating-harnessloader-for-elements-that-use-content-projection)

Some components project additional content into the component's template. See the [content projection guide](guide/components/content-projection) for more information.

Add a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance scoped to the element containing the `<ng-content>` when you create a harness for a component that uses content projection. This allows the user of the harness to load additional harnesses for whatever components were passed in as content. [`ComponentHarness`](/api/cdk/testing/ComponentHarness) has several methods that can be used to create HarnessLoader instances for cases like this: `harnessLoaderFor()`, `harnessLoaderForOptional()`, `harnessLoaderForAll()`. See the [HarnessLoader interface API reference page](/api/cdk/testing/HarnessLoader) for more details.

For example, the `MyPopupHarness` example from above can extend [`ContentContainerComponentHarness`](/api/cdk/testing/ContentContainerComponentHarness) to add support to load harnesses within the `<ng-content>` of the component.

```
class MyPopupHarness extends ContentContainerComponentHarness<string> {
  static hostSelector = 'my-popup';
}
```

## [Accessing elements outside of the component's host element](#accessing-elements-outside-of-the-components-host-element)

There are times when a component harness might need to access elements outside of its corresponding component's host element. For example, code that displays a floating element or pop-up often attaches DOM elements directly to the document body, such as the `Overlay` service in Angular CDK.

In this case, [`ComponentHarness`](/api/cdk/testing/ComponentHarness) provides a method that can be used to get a [`LocatorFactory`](/api/cdk/testing/LocatorFactory) for the root element of the document. The [`LocatorFactory`](/api/cdk/testing/LocatorFactory) supports most of the same APIs as the [`ComponentHarness`](/api/cdk/testing/ComponentHarness) base class, and can then be used to query relative to the document's root element.

Consider if the `MyPopup` component above used the CDK overlay for the popup content, rather than an element in its own template. In this case, `MyPopupHarness` would have to access the content element via `documentRootLocatorFactory()` method that gets a locator factory rooted at the document root.

```
class MyPopupHarness extends ComponentHarness {
  static hostSelector = 'my-popup';

  /** Gets a `HarnessLoader` whose root element is the popup's content element. */
  async getHarnessLoaderForContent(): Promise<HarnessLoader> {
    const rootLocator = this.documentRootLocatorFactory();
    return rootLocator.harnessLoaderFor('my-popup-content');
  }
}
```

## [Waiting for asynchronous tasks](#waiting-for-asynchronous-tasks)

The methods on [`TestElement`](/api/cdk/testing/TestElement) automatically trigger Angular's change detection and wait for tasks inside the [`NgZone`](/api/core/NgZone). In most cases no special effort is required for harness authors to wait on asynchronous tasks. However, there are some edge cases where this may not be sufficient.

Under some circumstances, Angular animations may require a second cycle of change detection and subsequent [`NgZone`](/api/core/NgZone) stabilization before animation events are fully flushed. In cases where this is needed, the [`ComponentHarness`](/api/cdk/testing/ComponentHarness) offers a `forceStabilize()` method that can be called to do the second round.

You can use [`NgZone.runOutsideAngular()`](/api/core/NgZone#runOutsideAngular) to schedule tasks outside of NgZone. Call the `waitForTasksOutsideAngular()` method on the corresponding harness if you need to explicitly wait for tasks outside [`NgZone`](/api/core/NgZone) since this does not happen automatically.

----
url: https://angular.dev/api/common/getLocaleId
----

Angular recommends relying on the `Intl` API for i18n. This function serves no purpose when relying on the `Intl` API.

## [API](#api)

```
function getLocaleId(locale: string): string;
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. This function serves no purpose when relying on the `Intl` API.

@paramlocale`string`

A locale code, such as `fr-FR`.

@returns`string`

The locale code. For example, `fr`.

Jump to details

----
url: https://angular.dev/api/router/withRouterResources
----

```
function withRouterResources(): RouterResourcesFeature;
```

@returns`RouterResourcesFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can enable the feature:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withRouterResources())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/guide/testing/karma
----

While [Vitest](https://vitest.dev) is the default test runner for new Angular projects, [Karma](https://karma-runner.github.io) is still a supported and widely used test runner. This guide provides instructions for testing your Angular application using the Karma test runner with the [Jasmine](https://jasmine.github.io) testing framework.

## [Setting Up Karma and Jasmine](#setting-up-karma-and-jasmine)

You can set up Karma and Jasmine for a new project or add it to an existing one.

### [For New Projects](#for-new-projects)

To create a new project with Karma and Jasmine pre-configured, run the `ng new` command with the `--test-runner=karma` option:

```
ng new my-karma-app --test-runner=karma
```

### [For Existing Projects](#for-existing-projects)

To add Karma and Jasmine to an existing project, follow these steps:

1. **Install the necessary packages:**

   ### npm

   ```
   npm install --save-dev karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core @types/jasmine
   ```

   ### yarn

   ```
   yarn add --dev karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core @types/jasmine
   ```

   ### pnpm

   ```
   pnpm add -D karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core @types/jasmine
   ```

   ### bun

   ```
   bun add --dev karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core @types/jasmine
   ```

2. **Configure the test runner in `angular.json`:**

   In your `angular.json` file, find the `test` target and set the `runner` option to `karma`:

   ```
   {
     // ...
     "projects": {
       "your-project-name": {
         // ...
         "architect": {
           "test": {
             "builder": "@angular/build:unit-test",
             "options": {
               "runner": "karma"
               // ... other options
             }
           }
         }
       }
     }
   }
   ```

3. **Update `tsconfig.spec.json` for Jasmine types:**

   To ensure TypeScript recognizes global testing functions like `describe` and `it`, add `"jasmine"` to the `types` array in your `tsconfig.spec.json`:

   ```
   {
     // ...
     "compilerOptions": {
       // ...
       "types": ["jasmine"]
     }
     // ...
   }
   ```

## [Running Tests](#running-tests)

Once your project is configured, run the tests using the [`ng test`](cli/test) command:

```
ng test
```

The `ng test` command builds the application in *watch mode* and launches the [Karma test runner](https://karma-runner.github.io).

The console output looks like below:

```
02 11 2022 09:08:28.605:INFO [karma-server]: Karma v6.4.1 server started at http://localhost:9876/
02 11 2022 09:08:28.607:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
02 11 2022 09:08:28.620:INFO [launcher]: Starting browser Chrome
02 11 2022 09:08:31.312:INFO [Chrome]: Connected on socket -LaEYvD2R7MdcS0-AAAB with id 31534482
Chrome: Executed 3 of 3 SUCCESS (0.193 secs / 0.172 secs)
TOTAL: 3 SUCCESS
```

The test output is displayed in the browser using [Karma Jasmine HTML Reporter](https://github.com/dfederm/karma-jasmine-html-reporter).

Click on a test row to re-run just that test or click on a description to re-run the tests in the selected test group ("test suite").

Meanwhile, the `ng test` command is watching for changes. To see this in action, make a small change to a source file and save. The tests run again, the browser refreshes, and the new test results appear.

## [Configuration](#configuration)

The Angular CLI takes care of Jasmine and Karma configuration for you. It constructs the full configuration in memory, based on options specified in the `angular.json` file.

### [Customizing Karma Configuration](#customizing-karma-configuration)

If you want to customize Karma, you can create a `karma.conf.js` by running the following command:

```
ng generate config karma
```

**HELPFUL:** Read more about Karma configuration in the [Karma configuration guide](https://karma-runner.github.io/6.4/config/configuration-file.html).

### [Setting the Test Runner in `angular.json`](#setting-the-test-runner-in-angularjson)

To explicitly set Karma as the test runner for your project, locate the `test` target in your `angular.json` file and set the `runner` option to `karma`:

```
{
  // ...
  "projects": {
    "your-project-name": {
      // ...
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "runner": "karma"
            // ... other options
          }
        }
      }
    }
  }
}
```

## [Code coverage enforcement](#code-coverage-enforcement)

To enforce a minimum code coverage level, you can use the `check` property in the `coverageReporter` section of your `karma.conf.js` file.

For example, to require a minimum of 80% coverage:

```
coverageReporter: {
  dir: require('path').join(__dirname, './coverage/<project-name>'),
  subdir: '.',
  reporters: [
    { type: 'html' },
    { type: 'text-summary' }
  ],
  check: {
    global: {
      statements: 80,
      branches: 80,
      functions: 80,
      lines: 80
    }
  }
}
```

This will cause the test run to fail if the specified coverage thresholds are not met.

## [Testing in continuous integration](#testing-in-continuous-integration)

To run your Karma tests in a CI environment, use the following command:

```
ng test --no-watch --no-progress --browsers=ChromeHeadless
```

**NOTE:** The `--no-watch` and `--no-progress` flags are crucial for Karma in CI environments to ensure tests run once and exit cleanly. The `--browsers=ChromeHeadless` flag is also essential for running tests in a browser environment without a graphical interface.

## [Debugging tests](#debugging-tests)

If your tests aren't working as you expect, you can inspect and debug them in the browser.

To debug an application with the Karma test runner:

1. Reveal the Karma browser window. See [Set up for testing](guide/testing#set-up-for-testing) if you need help with this step.
2. Click the **DEBUG** button to open a new browser tab and re-run the tests.
3. Open the browser's **Developer Tools**. On Windows, press `Ctrl-Shift-I`. On macOS, press `Command-Option-I`.
4. Pick the **Sources** section.
5. Press `Control/Command-P`, and then start typing the name of your test file to open it.
6. Set a breakpoint in the test.
7. Refresh the browser, and notice how it stops at the breakpoint.

----
url: https://angular.dev/api/forms/signals/IgnoreUnknownProperties
----

```
    
      type IgnoreUnknownProperties<T> = T extends Record<PropertyKey, unknown>    ? {        [K in keyof T as RemoveStringIndexUnknownKey<K, T[K]>]: IgnoreUnknownProperties<T[K]>;      }    : T
    
    
```

Jump to details

----
url: https://angular.dev/api/core/AfterContentInit
----

```
    
      interface AfterContentInit {}
    
    
```

A callback method that is invoked immediately after Angular has completed initialization of all of the directive's content. It is invoked only once when the directive is instantiated.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define its own content initialization method.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements AfterContentInit {
        ngAfterContentInit() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/api/forms/ValueChangeEvent
----

```
    
      class ValueChangeEvent<T> extends ControlEvent<T> {}
    
    
```

@paramvalue`T`

@paramsource`AbstractControl<any, any, any>`

@returns`ValueChangeEvent<T>`

Jump to details

----
url: https://angular.dev/api/platform-server/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/guide/routing/customizing-route-behavior
----

Angular Router provides powerful extension points that allow you to customize how routes behave in your application. While the default routing behavior works well for most applications, specific requirements often demand custom implementations for performance optimization, specialized URL handling, or complex routing logic.

Route customization can become valuable when your application needs:

* **Component state preservation** across navigations to avoid re-fetching data
* **Strategic lazy module loading** based on user behavior or network conditions
* **External URL integration** or handling Angular routes alongside legacy systems
* **Dynamic route matching** based on runtime conditions beyond simple path patterns

**NOTE:** Before implementing custom strategies, ensure the default router behavior doesn't meet your needs. Angular's default routing is optimized for common use cases and provides the best balance of performance and simplicity. Customizing route strategies can create additional code complexity and have performance implications on memory usage if not carefully managed.

## [Router configuration options](#router-configuration-options)

The [`withRouterConfig`](/api/router/withRouterConfig) or [`RouterModule.forRoot`](/api/router/RouterModule#forRoot) allows providing additional [`RouterConfigOptions`](/api/router/RouterConfigOptions) to adjust the Router’s behavior.

### [Handle canceled navigations](#handle-canceled-navigations)

`canceledNavigationResolution` controls how the Router restores browser history when a navigation is canceled. The default value is `'replace'`, which reverts to the pre-navigation URL with `location.replaceState`. In practice, this means that any time the address bar has already been updated for the navigation, such as with the browser back or forward buttons, the history entry is overwritten with the "rollback" if the navigation fails or is rejected by a guard. Switching to `'computed'` keeps the in-flight history index in sync with the Angular navigation, so canceling a back button navigation triggers a forward navigation (and vice versa) to return to the original page.

This setting is most helpful when your app uses `urlUpdateStrategy: 'eager'` or when guards frequently cancel popstate navigations initiated by the browser.

```
provideRouter(routes, withRouterConfig({canceledNavigationResolution: 'computed'}));
```

### [React to same-URL navigations](#react-to-same-url-navigations)

`onSameUrlNavigation` configures what should happen when the user asks to navigate to the current URL. The default `'ignore'` skips work, while `'reload'` instructs the Router to process the URL in its navigation pipeline rather than skipping it. Guard and resolver reruns are governed by [`runGuardsAndResolvers`](api/router/RunGuardsAndResolvers), and component reuse is determined by the [route reuse strategy](#route-reuse-strategy).

This is useful when you want repeated clicks on a list filter, left-nav item, or refresh button to trigger new data retrieval even though the URL does not change.

```
provideRouter(routes, withRouterConfig({onSameUrlNavigation: 'reload'}));
```

You can also control this behavior on individual navigations rather than globally. This allows you to keep the default of `'ignore'` while selectively enabling reload behavior for specific use cases:

```
router.navigate(['/some-path'], {onSameUrlNavigation: 'reload'});
```

**TIP:** If you use [route resources](/guide/routing/data-fetching-with-resources#reloading-resources-without-renavigation), you can refresh data on-demand by calling `.reload()` on the resource or by updating signals read in `params`, without needing to configure `onSameUrlNavigation` or trigger a router navigation.

### [Control parameter inheritance](#control-parameter-inheritance)

`paramsInheritanceStrategy` defines how route parameters and data flow from parent routes.

By default (`'always'`), child routes automatically inherit parameters, route data, and resolved values from parent routes. This ensures matrix parameters, route data, and resolved values are available further down the route tree—handy when you share contextual identifiers across feature areas such as:

```
/org/:orgId/projects/:projectId/customers/:customerId
```

```
export const routes: Routes = [
  {
    path: 'org/:orgId',
    component: Organization,
    children: [
      {
        path: 'projects/:projectId',
        component: Project,
        children: [
          {
            path: 'customers/:customerId',
            component: Customer,
          },
        ],
      },
    ],
  },
];
```

```
@Component({/* ... */})
export class Customer {
  private route = inject(ActivatedRoute);

  // All parent parameters are available directly
  orgId = this.route.snapshot.params['orgId'];
  projectId = this.route.snapshot.params['projectId'];
  customerId = this.route.snapshot.params['customerId'];
}
```

To restore the legacy behavior, set `paramsInheritanceStrategy` to `'emptyOnly'`. With `'emptyOnly'`, child routes inherit params only when their path is empty or the parent does not declare a component:

```
provideRouter(routes, withRouterConfig({paramsInheritanceStrategy: 'emptyOnly'}));
```

In that case, the `Customer` component has to read the parent parameters from its ancestor routes:

```
@Component({/* ... */})
export class Customer {
  private route = inject(ActivatedRoute);

  orgId = this.route.parent?.parent?.snapshot.params['orgId'];
  projectId = this.route.parent?.snapshot.params['projectId'];
  customerId = this.route.snapshot.params['customerId'];
}
```

### [Decide when the URL updates](#decide-when-the-url-updates)

`urlUpdateStrategy` determines when Angular writes to the browser address bar. The default `'deferred'` waits for a successful navigation before changing the URL. Use `'eager'` to update immediately when navigation starts. Eager updates make it easier to surface the attempted URL if navigation fails due to guards or errors, but can briefly show an in-progress URL if you have long-running guards.

Consider this when your analytics pipeline needs to see the attempted route even if guards block it.

```
provideRouter(routes, withRouterConfig({urlUpdateStrategy: 'eager'}));
```

### [Choose default query parameter handling](#choose-default-query-parameter-handling)

`defaultQueryParamsHandling` sets the fallback behavior for [`Router.createUrlTree`](/api/router/Router#createUrlTree) when the call does not specify `queryParamsHandling`. `'replace'` is the default and swaps out the existing query string. `'merge'` combines the provided values with the current ones, and `'preserve'` keeps the existing query parameters unless you explicitly supply new ones.

```
provideRouter(routes, withRouterConfig({defaultQueryParamsHandling: 'merge'}));
```

This is especially helpful for search and filter pages to automatically retain existing filters when additional parameters are provided.

### [Configure trailing slash handling](#configure-trailing-slash-handling)

By default, the [`Location`](/api/common/Location) service strips trailing slashes from URLs on read.

You can configure the [`Location`](/api/common/Location) service to force a trailing slash on all URLs written to the browser by providing the [`TrailingSlashPathLocationStrategy`](/api/common/TrailingSlashPathLocationStrategy) in your application.

```
import {LocationStrategy, TrailingSlashPathLocationStrategy} from '@angular/common';

bootstrapApplication(App, {
  providers: [{provide: LocationStrategy, useClass: TrailingSlashPathLocationStrategy}],
});
```

You can also force the [`Location`](/api/common/Location) service to never have a trailing slash on all URLs written to the browser by providing the [`NoTrailingSlashPathLocationStrategy`](/api/common/NoTrailingSlashPathLocationStrategy) in your application.

```
import {LocationStrategy, NoTrailingSlashPathLocationStrategy} from '@angular/common';

bootstrapApplication(App, {
  providers: [{provide: LocationStrategy, useClass: NoTrailingSlashPathLocationStrategy}],
});
```

These strategies only affect the URL written to the browser. [`Location.path()`](/api/common/Location#path) and [`Location.normalize()`](/api/common/Location#normalize) will continue to strip trailing slashes when reading the URL.

Angular Router exposes four main areas for customization:

## [Route reuse strategy](#route-reuse-strategy)

Route reuse strategy controls whether Angular destroys and recreates components during navigation or preserves them for reuse. By default, Angular destroys component instances when navigating away from a route and creates new instances when navigating back.

### [When to implement route reuse](#when-to-implement-route-reuse)

Custom route reuse strategies benefit applications that need:

* **Form state preservation** - Keep partially completed forms when users navigate away and return
* **Expensive data retention** - Avoid re-fetching large datasets or complex calculations
* **Scroll position maintenance** - Preserve scroll positions in long lists or infinite scroll implementations
* **Tab-like interfaces** - Maintain component state when switching between tabs

### [Creating a custom route reuse strategy](#creating-a-custom-route-reuse-strategy)

Angular's [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) class allows you to customize navigation behavior through the concept of "detached route handles."

"Detached route handles" are Angular's way of storing component instances and their entire view hierarchy. When a route is detached, Angular preserves the component instance, its child components, and all associated state in memory. This preserved state can later be reattached when navigating back to the route.

The [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) class provides the following methods that control the lifecycle of route components:

| Method                                                                         | Description                                                                                                 |
| ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| [`shouldDetach`](api/router/RouteReuseStrategy#shouldDetach)                   | Determines if a route should be stored for later reuse when navigating away                                 |
| [`store`](api/router/RouteReuseStrategy#store)                                 | Stores the detached route handle when `shouldDetach` returns true                                           |
| [`shouldAttach`](api/router/RouteReuseStrategy#shouldAttach)                   | Determines if a stored route should be reattached when navigating to it                                     |
| [`retrieve`](api/router/RouteReuseStrategy#retrieve)                           | Returns the previously stored route handle for reattachment                                                 |
| [`shouldReuseRoute`](api/router/RouteReuseStrategy#shouldReuseRoute)           | Determines if the router should reuse the current route instance instead of destroying it during navigation |
| [`shouldDestroyInjector`](api/router/RouteReuseStrategy#shouldDestroyInjector) | Determines if the router should destroy the injector of a detached route when it is no longer stored        |

The following example demonstrates a custom route reuse strategy that selectively preserves component state based on route metadata:

```
import {
  RouteReuseStrategy,
  Route,
  ActivatedRouteSnapshot,
  DetachedRouteHandle,
} from '@angular/router';
import {Injectable} from '@angular/core';

@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  private handlers = new Map<Route | null, DetachedRouteHandle>();

  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // Determines if a route should be stored for later reuse
    return route.data['reuse'] === true;
  }

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {
    // Stores the detached route handle when shouldDetach returns true
    if (handle && route.data['reuse'] === true) {
      const key = this.getRouteKey(route);
      this.handlers.set(key, handle);
    }
  }

  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    // Checks if a stored route should be reattached
    const key = this.getRouteKey(route);
    return route.data['reuse'] === true && this.handlers.has(key);
  }

  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
    // Returns the stored route handle for reattachment
    const key = this.getRouteKey(route);
    return route.data['reuse'] === true ? (this.handlers.get(key) ?? null) : null;
  }

  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    // Determines if the router should reuse the current route instance
    return future.routeConfig === curr.routeConfig;
  }

  private getRouteKey(route: ActivatedRouteSnapshot): Route | null {
    return route.routeConfig;
  }
}
```

### [Manually destroying detached route handles](#manually-destroying-detached-route-handles)

When implementing a custom [`RouteReuseStrategy`](/api/router/RouteReuseStrategy), you may need to manually destroy a [`DetachedRouteHandle`](/api/router/DetachedRouteHandle) if you decide to discard it without reattaching it. For example, if your strategy has a cache size limit or expires handles after a certain time, you must ensure the component and its state are properly destroyed to avoid memory leaks.

Since [`DetachedRouteHandle`](/api/router/DetachedRouteHandle) is an opaque type, you cannot call a destroy method directly on it. Instead, use the [`destroyDetachedRouteHandle`](/api/router/destroyDetachedRouteHandle) function provided by the Router.

```
import {destroyDetachedRouteHandle} from '@angular/router';

// ... inside your strategy
if (this.handles.size > MAX_CACHE_SIZE) {
  const handle = this.handles.get(oldestKey);
  if (handle) {
    destroyDetachedRouteHandle(handle);
    this.handles.delete(oldestKey);
  }
}
```

**NOTE:** Avoid using the route path as the key when `canMatch` guards are involved, as it may lead to duplicate entries.

### [Automatic cleanup of unused route injectors](#automatic-cleanup-of-unused-route-injectors)

By default, Angular does not destroy the injectors of detached routes, even if they are no longer stored by the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy). This is primarily because this level of memory management is not commonly needed for most applications.

To enable automatic cleanup of unused route injectors, you can use the [`withAutoCleanupInjectors`](/api/router/withAutoCleanupInjectors) feature in your router configuration. This feature checks which routes are currently stored by the strategy after navigations and destroys the injectors of any detached routes that are not currently stored by the reuse strategy.

```
import {provideRouter, withAutoCleanupInjectors} from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes, withAutoCleanupInjectors())],
};
```

If you do not provide a custom [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) or your custom strategy extends [`BaseRouteReuseStrategy`](/api/router/BaseRouteReuseStrategy), injectors will now be destroyed when the route is inactive.

#### [Cleanup with a custom `RouteReuseStrategy`](#cleanup-with-a-custom-routereusestrategy)

If your application uses a custom [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) *and* the strategy does not extend [`BaseRouteReuseStrategy`](/api/router/BaseRouteReuseStrategy), you must implement `shouldDestroyInjector` to tell the router which routes should have their injectors destroyed:

```
@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  // ... other methods

  shouldDestroyInjector(route: Route): boolean {
    return !route.data['retainInjector'];
  }
}
```

If your strategy ever stores a [`DetachedRouteHandle`](/api/router/DetachedRouteHandle), you will also need to tell the Router about these so it does not destroy any injectors needed by that detached handle:

```
@Injectable()
export class CustomRouteReuseStrategy implements RouteReuseStrategy {
  private readonly handles = new Map<Route, DetachedRouteHandle>();

  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null) {
    this.handles.set(route.routeConfig!, handle);
  }

  retrieveStoredRouteHandles(): DetachedRouteHandle[] {
    return Array.from(this.handles.values());
  }

  // ... other methods
}
```

### [Configuring a route to use a custom route reuse strategy](#configuring-a-route-to-use-a-custom-route-reuse-strategy)

Routes can opt into reuse behavior through route configuration metadata. This approach keeps the reuse logic separate from component code, making it easy to adjust behavior without modifying components:

```
export const routes: Routes = [
  {
    path: 'products',
    component: ProductList,
    data: {reuse: true}, // Component state persists across navigations
  },
  {
    path: 'products/:id',
    component: ProductDetail,
    // No reuse flag - component recreates on each navigation
  },
  {
    path: 'search',
    component: Search,
    data: {reuse: true}, // Preserves search results and filter state
  },
];
```

You can also configure a custom route reuse strategy at the application level through Angular's dependency injection system. In this case, Angular creates a single instance of the strategy that manages all route reuse decisions throughout the application:

```
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    {provide: RouteReuseStrategy, useClass: CustomRouteReuseStrategy},
  ],
};
```

## [Preloading strategy](#preloading-strategy)

Preloading strategies determine when Angular loads lazy-loaded route modules in the background. While lazy loading improves initial load time by deferring module downloads, users still experience a delay when first navigating to a lazy route. Preloading strategies eliminate this delay by loading modules before users request them.

### [Built-in preloading strategies](#built-in-preloading-strategies)

Angular provides two preloading strategies out of the box:

| Strategy                                            | Description                                                                                                      |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [`NoPreloading`](api/router/NoPreloading)           | The default strategy that disables all preloading. In other words, modules only load when users navigate to them |
| [`PreloadAllModules`](api/router/PreloadAllModules) | Loads all lazy-loaded modules immediately after the initial navigation                                           |

The [`PreloadAllModules`](/api/router/PreloadAllModules) strategy can be configured as follows:

```
import {ApplicationConfig} from '@angular/core';
import {provideRouter, withPreloading, PreloadAllModules} from '@angular/router';
import {routes} from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes, withPreloading(PreloadAllModules))],
};
```

The [`PreloadAllModules`](/api/router/PreloadAllModules) strategy works well for small to medium applications where downloading all modules doesn't significantly impact performance. However, larger applications with many feature modules might benefit from more selective preloading.

### [Creating a custom preloading strategy](#creating-a-custom-preloading-strategy)

Custom preloading strategies implement the [`PreloadingStrategy`](/api/router/PreloadingStrategy) interface, which requires a single `preload` method. This method receives the route configuration and a function that triggers the actual module load. The strategy returns an Observable that emits when preloading completes or an empty Observable to skip preloading:

```
import {Injectable} from '@angular/core';
import {PreloadingStrategy, Route} from '@angular/router';
import {Observable, of, timer} from 'rxjs';
import {mergeMap} from 'rxjs/operators';

@Injectable()
export class SelectivePreloadingStrategy implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    // Only preload routes marked with data: { preload: true }
    if (route.data?.['preload']) {
      return load();
    }
    return of(null);
  }
}
```

This selective strategy checks route metadata to determine preloading behavior. Routes can opt into preloading through their configuration:

```
import {Routes} from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadChildren: () => import('./dashboard/dashboard.routes'),
    data: {preload: true}, // Preload immediately after initial navigation
  },
  {
    path: 'reports',
    loadChildren: () => import('./reports/reports.routes'),
    data: {preload: false}, // Only load when user navigates to reports
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.routes'),
    // No preload flag - won't be preloaded
  },
];
```

### [Performance considerations for preloading](#performance-considerations-for-preloading)

Preloading impacts both network usage and memory consumption. Each preloaded module consumes bandwidth and increases the application's memory footprint. Mobile users on metered connections might prefer minimal preloading, while desktop users on fast networks can handle aggressive preloading strategies.

The timing of preloading also matters. Immediate preloading after initial load might compete with other critical resources like images or API calls. Strategies should consider the application's post-load behavior and coordinate with other background tasks to avoid performance degradation.

Browser resource limits also affect preloading behavior. Browsers limit concurrent HTTP connections, so aggressive preloading might queue behind other requests. Service workers can help by providing fine-grained control over caching and network requests, complementing the preloading strategy.

## [URL handling strategy](#url-handling-strategy)

URL handling strategies determine which URLs the Angular router processes versus which ones it ignores. By default, Angular attempts to handle all navigation events within the application, but real-world applications often need to coexist with other systems, handle external links, or integrate with legacy applications that manage their own routes.

The [`UrlHandlingStrategy`](/api/router/UrlHandlingStrategy) class gives you control over this boundary between Angular-managed routes and external URLs. This becomes essential when migrating applications to Angular incrementally or when Angular applications need to share URL space with other frameworks.

### [Implementing a custom URL handling strategy](#implementing-a-custom-url-handling-strategy)

Custom URL handling strategies extend the [`UrlHandlingStrategy`](/api/router/UrlHandlingStrategy) class and implement three methods. The `shouldProcessUrl` method determines whether Angular should handle a given URL, `extract` returns the portion of the URL that Angular should process, and `merge` combines the URL fragment with the rest of the URL:

```
import {Injectable} from '@angular/core';
import {UrlHandlingStrategy, UrlTree} from '@angular/router';

@Injectable()
export class CustomUrlHandlingStrategy implements UrlHandlingStrategy {
  shouldProcessUrl(url: UrlTree): boolean {
    // Only handle URLs that start with /app or /admin
    return url.toString().startsWith('/app') || url.toString().startsWith('/admin');
  }

  extract(url: UrlTree): UrlTree {
    // Return the URL unchanged if we should process it
    return url;
  }

  merge(newUrlPart: UrlTree, rawUrl: UrlTree): UrlTree {
    // Combine the URL fragment with the rest of the URL
    return newUrlPart;
  }
}
```

This strategy creates clear boundaries in the URL space. Angular handles `/app` and `/admin` paths while ignoring everything else. This pattern works well when migrating legacy applications where Angular controls specific sections while the legacy system maintains others.

### [Configuring a custom URL handling strategy](#configuring-a-custom-url-handling-strategy)

You can register a custom strategy through Angular's dependency injection system:

```
import {ApplicationConfig} from '@angular/core';
import {provideRouter, UrlHandlingStrategy} from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    {provide: UrlHandlingStrategy, useClass: CustomUrlHandlingStrategy},
  ],
};
```

## [Custom route matchers](#custom-route-matchers)

By default, Angular's router iterates through routes in the order they're defined, attempting to match the URL path against each route's path pattern. It supports static segments, parameterized segments (`:id`), and wildcards (`**`). The first route that matches wins, and the router stops searching.

When applications require more sophisticated matching logic based on runtime conditions, complex URL patterns, or other custom rules, custom matchers provide this flexibility without compromising the simplicity of standard routes.

The router evaluates custom matchers during the route matching phase, before path matching occurs. When a matcher returns a successful match, it can also extract parameters from the URL, making them available to the activated component just like standard route parameters.

### [Creating a custom matcher](#creating-a-custom-matcher)

A custom matcher is a function that receives URL segments and returns either a match result with consumed segments and parameters, or null to indicate no match. The matcher function runs before Angular evaluates the route's path property:

```
import {Route, UrlSegment, UrlSegmentGroup, UrlMatchResult} from '@angular/router';

export function customMatcher(
  segments: UrlSegment[],
  group: UrlSegmentGroup,
  route: Route,
): UrlMatchResult | null {
  // Matching logic here
  if (matchSuccessful) {
    return {
      consumed: segments,
      posParams: {
        paramName: new UrlSegment('paramValue', {}),
      },
    };
  }
  return null;
}
```

### [Implementing version-based routing](#implementing-version-based-routing)

Consider an API documentation site that needs to route based on version numbers in the URL. Different versions might have different component structures or feature sets:

```
import {Routes, UrlSegment, UrlMatchResult} from '@angular/router';

export function versionMatcher(segments: UrlSegment[]): UrlMatchResult | null {
  // Match patterns like /v1/docs, /v2.1/docs, /v3.0.1/docs
  if (segments.length >= 2 && segments[0].path.match(/^v\d+(\.\d+)*$/)) {
    return {
      consumed: segments.slice(0, 2), // Consume version and 'docs'
      posParams: {
        version: segments[0], // Make version available as a parameter
        section: segments[1], // Make section available too
      },
    };
  }
  return null;
}

// Route configuration
export const routes: Routes = [
  {
    matcher: versionMatcher,
    component: Documentation,
  },
  {
    path: 'latest/docs',
    redirectTo: 'v3/docs',
  },
];
```

The component receives the extracted parameters through route inputs:

```
import {Component, input, inject, resource} from '@angular/core';

@Component({
  selector: 'app-documentation',
  template: `
    @if (documentation.isLoading()) {
      <div>Loading documentation...</div>
    } @else if (documentation.error()) {
      <div>Error loading documentation</div>
    } @else if (documentation.value(); as docs) {
      <article>{{ docs.content }}</article>
    }
  `,
})
export class Documentation {
  // Route parameters are automatically bound to signal inputs
  version = input.required<string>(); // Receives the version parameter
  section = input.required<string>(); // Receives the section parameter

  private docsService = inject(DocumentationService);

  // Resource automatically loads documentation when version or section changes
  documentation = resource({
    params: () => {
      if (!this.version() || !this.section()) return;

      return {
        version: this.version(),
        section: this.section(),
      };
    },
    loader: ({params}) => {
      return this.docsService.loadDocumentation(params.version, params.section);
    },
  });
}
```

### [Locale-aware routing](#locale-aware-routing)

International applications often encode locale information in URLs. A custom matcher can extract locale codes and route to appropriate components while making the locale available as a parameter:

```
// Supported locales
const locales = ['en', 'es', 'fr', 'de', 'ja', 'zh'];

export function localeMatcher(segments: UrlSegment[]): UrlMatchResult | null {
  if (segments.length > 0) {
    const potentialLocale = segments[0].path;

    if (locales.includes(potentialLocale)) {
      // This is a locale prefix, consume it and continue matching
      return {
        consumed: [segments[0]],
        posParams: {
          locale: segments[0],
        },
      };
    } else {
      // No locale prefix, use default locale
      return {
        consumed: [], // Don't consume any segments
        posParams: {
          locale: new UrlSegment('en', {}),
        },
      };
    }
  }

  return null;
}
```

### [Complex business logic matching](#complex-business-logic-matching)

Custom matchers excel at implementing business rules that would be awkward to express in path patterns. Consider an e-commerce site where product URLs follow different patterns based on product type:

```
export function productMatcher(segments: UrlSegment[]): UrlMatchResult | null {
  if (segments.length === 0) return null;

  const firstSegment = segments[0].path;

  // Books: /isbn-1234567890
  if (firstSegment.startsWith('isbn-')) {
    return {
      consumed: [segments[0]],
      posParams: {
        productType: new UrlSegment('book', {}),
        identifier: new UrlSegment(firstSegment.substring(5), {}),
      },
    };
  }

  // Electronics: /sku/ABC123
  if (firstSegment === 'sku' && segments.length > 1) {
    return {
      consumed: segments.slice(0, 2),
      posParams: {
        productType: new UrlSegment('electronics', {}),
        identifier: segments[1],
      },
    };
  }

  // Clothing: /style/BRAND/ITEM
  if (firstSegment === 'style' && segments.length > 2) {
    return {
      consumed: segments.slice(0, 3),
      posParams: {
        productType: new UrlSegment('clothing', {}),
        brand: segments[1],
        identifier: segments[2],
      },
    };
  }

  return null;
}
```

### [Performance considerations for custom matchers](#performance-considerations-for-custom-matchers)

Custom matchers run for every navigation attempt until a match is found. As a result, complex matching logic can impact navigation performance, especially in applications with many routes. Keep matchers focused and efficient:

* Return early when a match is impossible
* Avoid expensive operations like API calls or complex regular expressions
* Consider caching results for repeated URL patterns

While custom matchers solve complex routing requirements elegantly, overuse can make route configuration harder to understand and maintain. Reserve custom matchers for scenarios where standard path matching genuinely falls short.

----
url: https://angular.dev/api/platform-browser/EventManager
----

```
    
      class EventManager {}
    
    
```

Initializes an instance of the event-manager service.

@paramplugins`EventManagerPlugin[]`

@param\_zone`NgZone`

@returns`EventManager`

Registers a handler for a specific element and event.

@paramelement`HTMLElement`

The HTML element to receive event notifications.

@parameventName`string`

The name of the event to listen for.

@paramhandler`Function`

A function to call when the notification occurs. Receives the event object as an argument.

@paramoptions`ListenerOptions | undefined`

Options that configure how the event listener is bound.

@returns`Function`

A callback function that can be used to remove the handler.

Retrieves the compilation zone in which event listeners are registered.

@returns`NgZone`

Jump to details

----
url: https://angular.dev/guide/http/interceptors
----

[`HttpClient`](/api/common/http/HttpClient) supports a form of middleware known as *interceptors*.

**TL;DR:** Interceptors are middleware that allows common patterns around retrying, caching, logging, and authentication to be abstracted away from individual requests.

[`HttpClient`](/api/common/http/HttpClient) supports two kinds of interceptors: functional and DI-based. Our recommendation is to use functional interceptors because they have more predictable behavior, especially in complex setups. Our examples in this guide use functional interceptors, and we cover [DI-based interceptors](#di-based-interceptors) in their own section at the end.

## [Interceptors](#interceptors)

Interceptors are generally functions that you can run for each request and have broad capabilities to affect the contents and overall flow of requests and responses. You can install multiple interceptors, which form an interceptor chain where each interceptor processes the request or response before forwarding it to the next interceptor in the chain.

You can use interceptors to implement a variety of common patterns, such as:

* Adding authentication headers to outgoing requests to a particular API.
* Retrying failed requests with exponential backoff.
* Caching responses for a period of time, or until invalidated by mutations.
* Customizing the parsing of responses.
* Measuring server response times and logging them.
* Driving UI elements such as a loading spinner while network operations are in progress.
* Collecting and batching requests made within a certain timeframe.
* Automatically failing requests after a configurable deadline or timeout.
* Regularly polling the server and refreshing results.

## [Defining an interceptor](#defining-an-interceptor)

The basic form of an interceptor is a function which receives the outgoing [`HttpRequest`](/api/common/http/HttpRequest) and a `next` function representing the next processing step in the interceptor chain.

For example, this `loggingInterceptor` will log the outgoing request URL to `console.log` before forwarding the request:

```
export function loggingInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  console.log(req.url);
  return next(req);
}
```

In order for this interceptor to actually intercept requests, you must configure [`HttpClient`](/api/common/http/HttpClient) to use it.

## [Configuring interceptors](#configuring-interceptors)

You declare the set of interceptors to use when configuring [`HttpClient`](/api/common/http/HttpClient) through dependency injection, by using the [`withInterceptors`](/api/common/http/withInterceptors) feature:

```
bootstrapApplication(App, {
  providers: [provideHttpClient(withInterceptors([loggingInterceptor, cachingInterceptor]))],
});
```

The interceptors you configure are chained together in the order that you've listed them in the providers. In the above example, the `loggingInterceptor` would process the request and then forward it to the `cachingInterceptor`.

### [Intercepting response events](#intercepting-response-events)

An interceptor may transform the `Observable` stream of [`HttpEvent`](/api/common/http/HttpEvent)s returned by `next` in order to access or manipulate the response. Because this stream includes all response events, inspecting the `.type` of each event may be necessary in order to identify the final response object.

```
export function loggingInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response) {
        console.log(req.url, 'returned a response with status', event.status);
      }
    }),
  );
}
```

**TIP:** Interceptors naturally associate responses with their outgoing requests, because they transform the response stream in a closure that captures the request object.

## [Modifying requests](#modifying-requests)

Most aspects of [`HttpRequest`](/api/common/http/HttpRequest) and [`HttpResponse`](/api/common/http/HttpResponse) instances are *immutable*, and interceptors cannot directly modify them. Instead, interceptors apply mutations by cloning these objects using the `.clone()` operation, and specifying which properties should be mutated in the new instance. This might involve performing immutable updates on the value itself (like [`HttpHeaders`](/api/common/http/HttpHeaders) or [`HttpParams`](/api/common/http/HttpParams)).

For example, to add a header to a request:

```
const reqWithHeader = req.clone({
  headers: req.headers.set('X-New-Header', 'new header value'),
});
```

This immutability allows most interceptors to be idempotent if the same [`HttpRequest`](/api/common/http/HttpRequest) is submitted to the interceptor chain multiple times. This can happen for a few reasons, including when a request is retried after failure.

**CRITICAL:** The body of a request or response is **not** protected from deep mutations. If an interceptor must mutate the body, take care to handle running multiple times on the same request.

## [Dependency injection in interceptors](#dependency-injection-in-interceptors)

Interceptors are run in the *injection context* of the injector which registered them, and can use Angular's [`inject`](/api/core/inject) API to retrieve dependencies.

For example, suppose an application has a service called `AuthService`, which creates authentication tokens for outgoing requests. An interceptor can inject and use this service:

```
export function authInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn) {
  // Inject the current `AuthService` and use it to get an authentication token:
  const authToken = inject(AuthService).getAuthToken();

  // Clone the request to add the authentication header.
  const newReq = req.clone({
    headers: req.headers.append('X-Authentication-Token', authToken),
  });
  return next(newReq);
}
```

## [Request and response metadata](#request-and-response-metadata)

Often it's useful to include information in a request that's not sent to the backend, but is specifically meant for interceptors. [`HttpRequest`](/api/common/http/HttpRequest)s have a `.context` object which stores this kind of metadata as an instance of [`HttpContext`](/api/common/http/HttpContext). This object functions as a typed map, with keys of type [`HttpContextToken`](/api/common/http/HttpContextToken).

To illustrate how this system works, let's use metadata to control whether a caching interceptor is enabled for a given request.

### [Defining context tokens](#defining-context-tokens)

To store whether the caching interceptor should cache a particular request in that request's `.context` map, define a new [`HttpContextToken`](/api/common/http/HttpContextToken) to act as a key:

```
export const CACHING_ENABLED = new HttpContextToken<boolean>(() => true);
```

The provided function creates the default value for the token for requests that haven't explicitly set a value for it. Using a function ensures that if the token's value is an object or array, each request gets its own instance.

### [Reading the token in an interceptor](#reading-the-token-in-an-interceptor)

An interceptor can then read the token and choose to apply caching logic or not based on its value:

```
export function cachingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
  if (req.context.get(CACHING_ENABLED)) {
    // apply caching logic
    return ...;
  } else {
    // caching has been disabled for this request
    return next(req);
  }
}
```

### [Setting context tokens when making a request](#setting-context-tokens-when-making-a-request)

When making a request via the [`HttpClient`](/api/common/http/HttpClient) API, you can provide values for [`HttpContextToken`](/api/common/http/HttpContextToken)s:

```
const data$ = http.get('/sensitive/data', {
  context: new HttpContext().set(CACHING_ENABLED, false),
});
```

Interceptors can read these values from the [`HttpContext`](/api/common/http/HttpContext) of the request.

### [The request context is mutable](#the-request-context-is-mutable)

Unlike other properties of [`HttpRequest`](/api/common/http/HttpRequest)s, the associated [`HttpContext`](/api/common/http/HttpContext) is *mutable*. If an interceptor changes the context of a request that is later retried, the same interceptor will observe the context mutation when it runs again. This is useful for passing state across multiple retries if needed.

## [Synthetic responses](#synthetic-responses)

Most interceptors will simply invoke the `next` handler while transforming either the request or the response, but this is not strictly a requirement. This section discusses several of the ways in which an interceptor may incorporate more advanced behavior.

Interceptors are not required to invoke `next`. They may instead choose to construct responses through some other mechanism, such as from a cache or by sending the request through an alternate mechanism.

Constructing a response is possible using the [`HttpResponse`](/api/common/http/HttpResponse) constructor:

```
const resp = new HttpResponse({
  body: 'response body',
});
```

## [Working with redirect information](#working-with-redirect-information)

When [`HttpClient`](/api/common/http/HttpClient) uses the fetch backend, responses include a `redirected` property that indicates whether the response was the result of a redirect. This property aligns with the native Fetch API specification and can be useful in interceptors for handling redirect scenarios.

An interceptor can access and act upon the redirect information:

```
export function redirectTrackingInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response && event.redirected) {
        console.log('Request to', req.url, 'was redirected to', event.url);
        // Handle redirect logic - maybe update analytics, security checks, etc.
      }
    }),
  );
}
```

You can also use the redirect information to implement conditional logic in your interceptors:

```
export function authRedirectInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response && event.redirected) {
        // Check if we were redirected to a login page
        if (event.url?.includes('/login')) {
          // Handle authentication redirect
          handleAuthRedirect();
        }
      }
    }),
  );
}
```

## [Working with response types](#working-with-response-types)

When [`HttpClient`](/api/common/http/HttpClient) uses the fetch backend, responses include a `responseType` property that indicates how the browser handled the response based on CORS policies and request mode. This property aligns with the native Fetch API specification and provides valuable insights for debugging CORS issues and understanding response accessibility.

The response `responseType` property can have the following values:

* `'basic'` - Same-origin response with all headers accessible
* `'cors'` - Cross-origin response with CORS headers properly configured
* `'opaque'` - Cross-origin response without CORS, headers and body may be limited
* `'opaqueredirect'` - Response from a redirected request in no-cors mode
* `'error'` - Network error occurred

An interceptor can use response type information for CORS debugging and error handling:

```
export function responseTypeInterceptor(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  return next(req).pipe(
    tap((event) => {
      if (event.type === HttpEventType.Response) {
        // Handle different response types appropriately
        switch (event.responseType) {
          case 'opaque':
            // Limited access to response data
            console.warn('Limited response data due to CORS policy');
            break;
          case 'cors':
          case 'basic':
            // Full access to response data
            break;
          case 'error':
            // Handle network errors
            console.error('Network error in response');
            break;
        }
      }
    }),
  );
}
```

## [DI-based interceptors](#di-based-interceptors)

[`HttpClient`](/api/common/http/HttpClient) also supports interceptors which are defined as injectable classes and configured through the DI system. The capabilities of DI-based interceptors are identical to those of functional interceptors, but the configuration mechanism is different.

A DI-based interceptor is an injectable class which implements the [`HttpInterceptor`](/api/common/http/HttpInterceptor) interface:

```
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, handler: HttpHandler): Observable<HttpEvent<any>> {
    console.log('Request URL: ' + req.url);
    return handler.handle(req);
  }
}
```

DI-based interceptors are configured through a dependency injection multi-provider:

```
bootstrapApplication(App, {
  providers: [
    provideHttpClient(
      // DI-based interceptors must be explicitly enabled.
      withInterceptorsFromDi(),
    ),

    {provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
  ],
});
```

DI-based interceptors run in the order that their providers are registered. In an app with an extensive and hierarchical DI configuration, this order can be very hard to predict.

----
url: https://angular.dev/api/common/PlatformLocation
----

```
    
      abstract class PlatformLocation {}
    
    
```

@returns`string`

@returns`unknown`

Returns a function that, when executed, removes the `popstate` event handler.

@paramfn`LocationChangeListener`

@returns`VoidFunction`

Returns a function that, when executed, removes the `hashchange` event handler.

## [Description](#description)

This class should not be used directly by an application developer. Instead, use [`Location`](/api/common/Location).

[`PlatformLocation`](/api/common/PlatformLocation) encapsulates all calls to DOM APIs, which allows the Router to be platform-agnostic. This means that we can have different implementation of [`PlatformLocation`](/api/common/PlatformLocation) for the different platforms that Angular supports. For example, `@angular/platform-browser` provides an implementation specific to the browser environment, while `@angular/platform-server` provides one suitable for use with server-side rendering.

The [`PlatformLocation`](/api/common/PlatformLocation) class is used directly by all implementations of [`LocationStrategy`](/api/common/LocationStrategy) when they need to interact with the DOM APIs like pushState, popState, etc.

[`LocationStrategy`](/api/common/LocationStrategy) in turn is used by the [`Location`](/api/common/Location) service which is used directly by the [`Router`](/api/router/Router) in order to navigate between routes. Since all interactions between [`Router`](/api/router/Router) / [`Location`](/api/common/Location) / [`LocationStrategy`](/api/common/LocationStrategy) and DOM APIs flow through the [`PlatformLocation`](/api/common/PlatformLocation) class, they are all platform-agnostic.

Jump to details

----
url: https://angular.dev/api/common/LocationChangeEvent
----

```
    
      interface LocationChangeEvent {}
    
    
```

Whether the user agent performed a visual transition for this navigation.

Jump to details

----
url: https://angular.dev/api/router/UrlCreationOptions
----

```
    
      interface UrlCreationOptions {}
    
    
```

Specifies a root URI to use for relative navigation.

For example, consider the following route configuration where the parent route has two children.

```
[{
  path: 'parent',
  component: ParentComponent,
  children: [{
    path: 'list',
    component: ListComponent
  },{
    path: 'child',
    component: ChildComponent
  }]
}]
```

The following `go()` function navigates to the `list` route by interpreting the destination URI as relative to the activated `child` route

```
@Component({...})
class ChildComponent {
  constructor(private router: Router, private route: ActivatedRoute) {}

  go() {
    router.navigate(['../list'], { relativeTo: this.route });
  }
}
```

A value of `null` or `undefined` indicates that the navigation commands should be applied relative to the root.

Sets query parameters to the URL.

```
// Navigate to /results?page=1
router.navigate(['/results'], { queryParams: { page: 1 } });
```

Sets the hash fragment for the URL.

```
// Navigate to /results#top
router.navigate(['/results'], { fragment: 'top' });
```

How to handle query parameters in the router link for the next navigation. One of:

* `preserve` : Preserve current parameters.
* `merge` : Merge new with current parameters.

The "preserve" option discards any new query params:

```
// from /view1?page=1 to/view2?page=1
router.navigate(['/view2'], { queryParams: { page: 2 },  queryParamsHandling: "preserve"
});
```

The "merge" option appends new query params to the params from the current URL:

```
// from /view1?page=1 to/view2?page=1&otherKey=2
router.navigate(['/view2'], { queryParams: { otherKey: 2 },  queryParamsHandling: "merge"
});
```

In case of a key collision between current parameters and those in the `queryParams` object, the new value is used.

When true, preserves the URL fragment for the next navigation

```
// Preserve fragment from /results#top to /view#top
router.navigate(['/view'], { preserveFragment: true });
```

Jump to details

----
url: https://angular.dev/api/core/PendingTasks
----

```
    
      class PendingTasks {}
    
    
```

Adds a new task that should block application's stability.

@returns`() => void`

A cleanup function that removes a task when called.

Runs an asynchronous function and blocks the application's stability until the function completes.

```
pendingTasks.run(async () => {
  const userData = await fetch('/api/user');
  this.userData.set(userData);
});
```

@paramfn`() => Promise<unknown>`

The asynchronous function to execute

@returns`void`

## [Description](#description)

Service that keeps track of pending tasks contributing to the stableness of Angular application. While several existing Angular services (ex.: `HttpClient`) will internally manage tasks influencing stability, this API gives control over stability to library and application developers for specific cases not covered by Angular internals.

The concept of stability comes into play in several important scenarios:

* SSR process needs to wait for the application stability before serializing and sending rendered HTML;
* tests might want to delay assertions until the application becomes stable;

## [Usage Notes](#usage-notes)

```
const pendingTasks = inject(PendingTasks);
const taskCleanup = pendingTasks.add();
// do work that should block application's stability and then:
taskCleanup();
```

Jump to details

----
url: https://angular.dev/api/upgrade/static/setAngularJSGlobal
----

```
function setAngularJSGlobal(ng: any): void;
```

@paramng`any`

@returns`void`

## [Description](#description)

Resets the AngularJS global.

Used when AngularJS is loaded lazily, and not available on `window`.

Jump to details

----
url: https://angular.dev/api/animations/browser/AnimationDriver
----

## [API](#api)

```
    
      abstract class AnimationDriver {}
    
    
```

@paramprop`string`

@returns`boolean`

@paramelm1`any`

@paramelm2`any`

@returns`boolean`

Obtains the parent element, if any. `null` is returned if the element does not have a parent.

@paramkeyframes`Map<string, string | number>[]`

@paramduration`number`

@paramdelay`number`

@parameasing`string | null | undefined`

@parampreviousPlayers`any[] | undefined`

@paramscrubberAccessRequested`boolean | undefined`

@returns`any`

@deprecated

Use the NoopAnimationDriver class.

Jump to details

----
url: https://angular.dev/api/router/RouterLinkActive
----

## [API](/api/router/RouterLinkActive#api)

```
    
      class RouterLinkActive implements OnChanges ,OnDestroy ,AfterContentInit {}
    
    
```

@paramrouter`Router`

@paramelement`ElementRef<any>`

@paramrenderer`Renderer2`

@paramcdr`ChangeDetectorRef`

@returns`RouterLinkActive`

Options to configure how to determine if the router link is active.

These options are passed to the [`isActive()`](/api/router/isActive) function.

When `undefined`, the default subset match behavior is used. When `null`, the link is never considered active regardless of the current URL.

Aria-current attribute to apply when the router link is active.

Possible values: `'page'` | `'step'` | `'location'` | `'date'` | `'time'` | `true` | `false`.

You can use the output `isActiveChange` to get notified each time the link becomes active or inactive.

Emits: true -> Route is active false -> Route is inactive

```
<a
 routerLink="/user/bob"
 routerLinkActive="active-link"
 (isActiveChange)="this.onRouterLinkActive($event)">Bob</a>
```

## [Description](/api/router/RouterLinkActive#description)

Tracks whether the linked route of an element is currently active, and allows you to specify one or more CSS classes to add to the element when the linked route is active.

Use this directive to create a visual distinction for elements associated with an active route. For example, the following code highlights the word "Bob" when the router activates the associated route:

```
<a routerLink="/user/bob" routerLinkActive="active-link">Bob</a>
```

Whenever the URL is either '/user' or '/user/bob', the "active-link" class is added to the anchor tag. If the URL changes, the class is removed.

You can set more than one class using a space-separated string or an array. For example:

```
<a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a>
<a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>
```

To add the classes only when the URL matches the link exactly, add the option `exact: true`:

```
<a routerLink="/user/bob" routerLinkActive="active-link" [routerLinkActiveOptions]="{exact:
true}">Bob</a>
```

To directly check the [`isActive`](/api/router/isActive) status of the link, assign the [`RouterLinkActive`](/api/router/RouterLinkActive) instance to a template variable. For example, the following checks the status without assigning any CSS classes:

```
<a routerLink="/user/bob" routerLinkActive #rla="routerLinkActive">
  Bob {{ rla.isActive ? '(already open)' : ''}}
</a>
```

You can apply the [`RouterLinkActive`](/api/router/RouterLinkActive) directive to an ancestor of linked elements. For example, the following sets the active-link class on the `<div>` parent tag when the URL is either '/user/jim' or '/user/bob'.

```
<div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}">
  <a routerLink="/user/jim">Jim</a>
  <a routerLink="/user/bob">Bob</a>
</div>
```

The [`RouterLinkActive`](/api/router/RouterLinkActive) directive can also be used to set the aria-current attribute to provide an alternative distinction for active elements to visually impaired users.

For example, the following code adds the 'active' class to the Home Page link when it is indeed active and in such case also sets its aria-current attribute to 'page':

```
<a routerLink="/" routerLinkActive="active" ariaCurrentWhenActive="page">Home Page</a>
```

**NOTE:** RouterLinkActive is a [`ContentChildren`](/api/core/ContentChildren) query. Content children queries do not retrieve elements or directives that are in other components' templates, since a component's template is always a black box to its ancestors.

## [Exported by](/api/router/RouterLinkActive#exported-by)

* `RouterModule`

Jump to details

----
url: https://angular.dev/errors/NG01101
----

Async validators must return a promise or an observable, and emit/resolve them whether the validation fails or succeeds. In particular, they must implement the [AsyncValidatorFn API](api/forms/AsyncValidator)

```
export function isTenAsync(control: AbstractControl): Observable<ValidationErrors | null> {
  const v: number = control.value;
  if (v !== 10) {
    // Emit an object with a validation error.
    return of({'notTen': true, 'requiredValue': 10});
  }
  // Emit null, to indicate no error occurred.
  return of(null);
}
```

## [Debugging the error](#debugging-the-error)

Did you mistakenly use a synchronous validator instead of an async validator?

----
url: https://angular.dev/extended-diagnostics/NG8109
----

This diagnostic detects uninvoked signals in template interpolations.

```
import {Component, signal, Signal} from '@angular/core';

@Component({
  template: `<div>{{ mySignal }}</div>`,
})
class MyComponent {
  mySignal: Signal<number> = signal(0);
}
```

## [What's wrong with that?](/extended-diagnostics/NG8109#whats-wrong-with-that)

Angular Signals are zero-argument functions (`() => T`). When executed, they return the current value of the signal. This means they are meant to be invoked when used in template interpolations to render their value.

## [What should I do instead?](/extended-diagnostics/NG8109#what-should-i-do-instead)

Ensure to invoke the signal when you use it within a template interpolation to render its value.

```
import {Component, signal, Signal} from '@angular/core';

@Component({
  template: `<div>{{ mySignal() }}</div>`,
})
class MyComponent {
  mySignal: Signal<number> = signal(0);
}
```

## [Configuration requirements](/extended-diagnostics/NG8109#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `interpolatedSignalNotInvoked` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](/extended-diagnostics/NG8109#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "interpolatedSignalNotInvoked": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/cdk/drag-drop/PreviewContainer
----

```
    
      type PreviewContainer = 'global' | 'parent' | ElementRef<HTMLElement> | HTMLElement
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/min
----

```
function min<
  TValue extends number | null,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  minValue: number | LogicFn<TValue, number | undefined, TPathKind>,
  config?: BaseValidatorConfig<TValue, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@paramminValue`number | LogicFn<TValue, number | undefined, TPathKind>`

The minimum value, or a LogicFn that returns the minimum value.

@paramconfig`BaseValidatorConfig<TValue, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`minError()`](/api/forms/signals/minError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/computed
----

```
function computed<T>(
  computation: () => T,
  options?: CreateComputedOptions<T> | undefined,
): Signal<T>;
```

@paramcomputation`() => T`

@paramoptions`CreateComputedOptions<T> | undefined`

@returns`Signal<T>`

Jump to details

----
url: https://angular.dev/api/core/testing/waitForAsync
----

```
function waitForAsync(fn: Function): (done: any) => any;
```

@paramfn`Function`

@returns`(done: any) => any`

## [Description](#description)

Wraps a test function in an asynchronous test zone. The test will automatically complete when all asynchronous calls within this zone are done. Can be used to wrap an [`inject`](/api/core/testing/inject) call.

Example:

```
it('...', waitForAsync(inject([AClass], (object) => {
  object.doSomething.then(() => {
    expect(...);
  })
})));
```

Jump to details

----
url: https://angular.dev/errors/NG02800
----

Angular produces this error when you attempt a `JSONP` request without providing the necessary support for it in the [`HttpClient`](/api/common/http/HttpClient) configuration. To enable `JSONP` support, you can do one of the following:

* add the [`withJsonpSupport()`](/api/common/http/withJsonpSupport) as an argument during the [`provideHttpClient`](/api/common/http/provideHttpClient) function call (e.g. `provideHttpClient(withJsonpSupport())`) when [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) is useddocs
* import the [`HttpClientJsonpModule`](/api/common/http/HttpClientJsonpModule) in your root AppModule, when NgModule-based bootstrap is used.

## [Debugging the error](#debugging-the-error)

Make sure that the JSONP support is added into your application either by calling the [`withJsonpSupport`](/api/common/http/withJsonpSupport) function (when [`provideHttpClient`](/api/common/http/provideHttpClient) is used) or importing the [`HttpClientJsonpModule`](/api/common/http/HttpClientJsonpModule) module as described above.

----
url: https://angular.dev/api/common/upgrade/LocationUpgradeConfig
----

```
    
      interface LocationUpgradeConfig {}
    
    
```

Configures whether the location upgrade module should use the `HashLocationStrategy` or the `PathLocationStrategy`

Configures the hash prefix used in the URL when using the `HashLocationStrategy`

Configures the URL codec for encoding and decoding URLs. Default is the `AngularJSCodec`

Configures the base href when used in server-side rendered applications

Configures the base href when used in client-side rendered applications

Jump to details

----
url: https://angular.dev/api/core/signal
----

```
function signal<T>(
  initialValue: T,
  options?: CreateSignalOptions<T> | undefined,
): WritableSignal<T>;
```

@paraminitialValue`T`

@paramoptions`CreateSignalOptions<T> | undefined`

@returns`WritableSignal<T>`

Jump to details

----
url: https://angular.dev/api/router/LoadChildrenCallback
----

```
    
      type LoadChildrenCallback = () =>  | Type<any>  | NgModuleFactory<any>  | Routes  | Observable<Type<any> | Routes | DefaultExport<Type<any>> | DefaultExport<Routes>>  | Promise<      NgModuleFactory<any> | Type<any> | Routes | DefaultExport<Type<any>> | DefaultExport<Routes>    >
    
    
```

## [Description](#description)

A function that is called to resolve a collection of lazy-loaded routes. Must be an arrow function of the following form: `() => import('...').then(mod => mod.MODULE)` or `() => import('...').then(mod => mod.ROUTES)`

For example:

```
[{
  path: 'lazy',
  loadChildren: () => import('./lazy-route/lazy.module').then(mod => mod.LazyModule),
}];
```

or

```
[{
  path: 'lazy',
  loadChildren: () => import('./lazy-route/lazy.routes').then(mod => mod.ROUTES),
}];
```

If the lazy-loaded routes are exported via a `default` export, the `.then` can be omitted:

```
[{
  path: 'lazy',
  loadChildren: () => import('./lazy-route/lazy.routes'),
}];
```

Jump to details

----
url: https://angular.dev/guide/image-optimization
----

- - [Overview](/guide/directives)
  - [Attribute directives](/guide/directives/attribute-directives)
  - [Structural directives](/guide/directives/structural-directives)
  - [Directive composition API](/guide/directives/directive-composition-api)
  - [Optimizing images with NgOptimizedImage](/guide/image-optimization)


   ```
   import {NgOptimizedImage} from '@angular/common';
   ```

   and include it into the `imports` array of a standalone component or an NgModule:

   ```
   imports: [
     NgOptimizedImage,
     // ...
   ],
   ```

2. ### [(Optional) Set up a Loader](#optional-set-up-a-loader)

   An image loader is not **required** in order to use NgOptimizedImage, but using one with an image CDN enables powerful performance features, including automatic `srcset`s for your images.

   A brief guide for setting up a loader can be found in the [Configuring an Image Loader](#configuring-an-image-loader-for-ngoptimizedimage) section at the end of this page.

3. ### [Enable the directive](#enable-the-directive)

   To activate the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive, replace your image's `src` attribute with [`ngSrc`](/api/common/NgOptimizedImage).

   ```
   <img ngSrc="cat.jpg" />
   ```

   If you're using a [built-in third-party loader](#built-in-loaders), make sure to omit the base URL path from `src`, as that will be prepended automatically by the loader.

4. ### [Mark images as `priority`](#mark-images-as-priority)

   Always mark the [LCP image](https://web.dev/lcp/#what-elements-are-considered) on your page as `priority` to prioritize its loading.

   ```
   <img ngSrc="cat.jpg" width="400" height="200" priority />
   ```

   ```
   <img ngSrc="cat.jpg" width="400" height="200" />
   ```

   For **responsive images** (images which you've styled to grow and shrink relative to the viewport), the `width` and `height` attributes should be the intrinsic size of the image file. For responsive images it's also important to [set a value for `sizes`.](#responsive-images)

   For **fixed size images**, the `width` and `height` attributes should reflect the desired rendered size of the image. The aspect ratio of these attributes should always match the intrinsic aspect ratio of the image.

   **NOTE:** If you don't know the size of your images, consider using "fill mode" to inherit the size of the parent container, as described below.

## [Using `fill` mode](#using-fill-mode)

In cases where you want to have an image fill a containing element, you can use the `fill` attribute. This is often useful when you want to achieve a "background image" behavior. It can also be helpful when you don't know the exact width and height of your image, but you do have a parent container with a known size that you'd like to fit your image into (see "object-fit" below).

When you add the `fill` attribute to your image, you do not need and should not include a `width` and `height`, as in this example:

```
<img ngSrc="cat.jpg" fill />
```

```
<img ngSrc="cat.jpg" width="400" height="200" placeholder />
```

Adding this attribute automatically requests a second, smaller version of the image using your specified image loader. This small image will be applied as a `background-image` style with a CSS blur while your image loads. If no image loader is provided, no placeholder image can be generated and an error will be thrown.

The default size for generated placeholders is 30px wide. You can change this size by specifying a pixel value in the [`IMAGE_CONFIG`](/api/common/IMAGE_CONFIG) provider, as seen below:

```
providers: [
  {
    provide: IMAGE_CONFIG,
    useValue: {
      placeholderResolution: 40
    }
  },
],
```

If you want sharp edges around your blurred placeholder, you can wrap your image in a containing `<div>` with the `overflow: hidden` style. As long as the `<div>` is the same size as the image (such as by using the `width: fit-content` style), the "fuzzy edges" of the placeholder will be hidden.

### [Data URL placeholders](#data-url-placeholders)

You can also specify a placeholder using a base64 [data URL](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) without an image loader. The data url format is `data:image/[imagetype];[data]`, where `[imagetype]` is the image format, just as `png`, and `[data]` is a base64 encoding of the image. That encoding can be done using the command line or in JavaScript. For specific commands, see [the MDN documentation](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URLs#encoding_data_into_base64_format). An example of a data URL placeholder with truncated data is shown below:

```
<img ngSrc="cat.jpg" width="400" height="200" placeholder="data:image/png;base64,iVBORw0K..." />
```

However, large data URLs increase the size of your Angular bundles and slow down page load. If you cannot use an image loader, the Angular team recommends keeping base64 placeholder images smaller than 4KB and using them exclusively on critical images. In addition to decreasing placeholder dimensions, consider changing image formats or parameters used when saving images. At very low resolutions, these parameters can have a large effect on file size.

### [Non-blurred placeholders](#non-blurred-placeholders)

By default, NgOptimizedImage applies a CSS blur effect to image placeholders. To render a placeholder without blur, provide a `placeholderConfig` argument with an object that includes the `blur` property, set to false. For example:

```
<img ngSrc="cat.jpg" width="400" height="200" placeholder [placeholderConfig]="{blur: false}" />
```

```
<link rel="preconnect" href="https://my.cdn.origin" />
```

To disable preconnect warnings, inject the [`PRECONNECT_CHECK_BLOCKLIST`](/api/common/PRECONNECT_CHECK_BLOCKLIST) token:

```
providers: [
{provide: PRECONNECT_CHECK_BLOCKLIST, useValue: 'https://your-domain.com'}
],
```

See more information on automatic preconnect generation [here](#why-is-a-preconnect-element-not-being-generated-for-my-image-domain).

### [Request images at the correct size with automatic `srcset`](#request-images-at-the-correct-size-with-automatic-srcset)

Defining a [`srcset` attribute](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/srcset) ensures that the browser requests an image at the right size for your user's viewport, so it doesn't waste time downloading an image that's too large. [`NgOptimizedImage`](/api/common/NgOptimizedImage) generates an appropriate `srcset` for the image, based on the presence and value of the [`sizes` attribute](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/sizes) on the image tag.

#### [Fixed-size images](#fixed-size-images)

If your image should be "fixed" in size (i.e. the same size across devices, except for [pixel density](https://web.dev/codelab-density-descriptors/)), there is no need to set a `sizes` attribute. A `srcset` can be generated automatically from the image's width and height attributes with no further input required.

Example srcset generated:

```
<img ... srcset="image-400w.jpg 1x, image-800w.jpg 2x" />
```

```
providers: [
  {
    provide: IMAGE_CONFIG,
    useValue: {
      breakpoints: [16, 48, 96, 128, 384, 640, 750, 828, 1080, 1200, 1920]
    }
  },
],
```

If you would like to manually define a `srcset` attribute, you can provide your own using the `ngSrcset` attribute:

```
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" />
```

If the `ngSrcset` attribute is present, [`NgOptimizedImage`](/api/common/NgOptimizedImage) generates and sets the `srcset` based on the sizes included. Do not include image file names in `ngSrcset` - the directive infers this information from [`ngSrc`](/api/common/NgOptimizedImage). The directive supports both width descriptors (e.g. `100w`) and density descriptors (e.g. `1x`).

```
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" sizes="50vw" />
```

### [Disabling automatic srcset generation](#disabling-automatic-srcset-generation)

To disable srcset generation for a single image, you can add the `disableOptimizedSrcset` attribute on the image:

```
<img ngSrc="about.jpg" disableOptimizedSrcset />
```

### [Disabling image lazy loading](#disabling-image-lazy-loading)

By default, [`NgOptimizedImage`](/api/common/NgOptimizedImage) sets `loading=lazy` for all images that are not marked `priority`. You can disable this behavior for non-priority images by setting the `loading` attribute. This attribute accepts values: `eager`, `auto`, and `lazy`. [See the documentation for the standard image `loading` attribute for details](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/loading#value).

```
<img ngSrc="cat.jpg" width="400" height="200" loading="eager" />
```

### [Controlling image decoding](#controlling-image-decoding)

By default, [`NgOptimizedImage`](/api/common/NgOptimizedImage) sets `decoding="auto"` for all images. This allows the browser to decide the optimal time to decode an image after it has been fetched. When an image is marked as `priority`, Angular automatically sets `decoding="sync"` to ensure the image is decoded and painted as early as possible helping improve **Largest Contentful Paint (LCP)** performance.

You can still override this behavior by explicitly setting the `decoding` attribute.\
[See the documentation for the standard image `decoding` attribute for details](https://developer.mozilla.org/docs/Web/HTML/Element/img#decoding).

```
<!-- Default: decoding is 'auto' -->
<img ngSrc="gallery/landscape.jpg" width="1200" height="800" />

<!-- Decode the image asynchronously to avoid blocking the main thread.-->
<img ngSrc="gallery/preview.jpg" width="600" height="400" decoding="async" />

<!-- Priority images automatically use decoding="sync" -->
<img ngSrc="awesome.jpg" width="500" height="625" priority />

<!-- Decode immediately (can block) when you need the pixels right away -->
<img ngSrc="hero.jpg" width="1600" height="900" decoding="sync" />
```

**Allowed values**

* `auto` (default): lets the browser choose the optimal strategy.
* `async`: decodes the image asynchronously, avoiding main‑thread blocking where possible.
* `sync`: decodes the image immediately; can block rendering but ensures pixels are ready as soon as the image is available.

### [Advanced 'sizes' values](#advanced-sizes-values)

You may want to have images displayed at varying widths on differently-sized screens. A common example of this pattern is a grid- or column-based layout that renders a single column on mobile devices, and two columns on larger devices. You can capture this behavior in the `sizes` attribute, using a "media query" syntax, such as the following:

```
<img ngSrc="cat.jpg" width="400" height="200" sizes="(max-width: 768px) 100vw, 50vw" />
```

The `sizes` attribute in the above example says "I expect this image to be 100 percent of the screen width on devices under 768px wide. Otherwise, I expect it to be 50 percent of the screen width.

For additional information about the `sizes` attribute, see [web.dev](https://web.dev/learn/design/responsive-images/#sizes) or [mdn](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/sizes).

## [Configuring an image loader for `NgOptimizedImage`](#configuring-an-image-loader-for-ngoptimizedimage)

A "loader" is a function that generates an [image transformation URL](https://web.dev/image-cdns/#how-image-cdns-use-urls-to-indicate-optimization-options) for a given image file. When appropriate, [`NgOptimizedImage`](/api/common/NgOptimizedImage) sets the size, format, and image quality transformations for an image.

[`NgOptimizedImage`](/api/common/NgOptimizedImage) provides both a generic loader that applies no transformations, as well as loaders for various third-party image services. It also supports writing your own custom loader.

| Loader type                            | Behavior                                                                                                                                                                                                                       |
| :------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Generic loader                         | The URL returned by the generic loader will always match the value of `src`. In other words, this loader applies no transformations. Sites that use Angular to serve images are the primary intended use case for this loader. |
| Loaders for third-party image services | The URL returned by the loaders for third-party image services will follow API conventions used by that particular image service.                                                                                              |
| Custom loaders                         | A custom loader's behavior is defined by its developer. You should use a custom loader if your image service isn't supported by the loaders that come preconfigured with [`NgOptimizedImage`](/api/common/NgOptimizedImage).   |

Based on the image services commonly used with Angular applications, [`NgOptimizedImage`](/api/common/NgOptimizedImage) provides loaders preconfigured to work with the following image services:

| Image Service             | Angular API                                                      | Documentation                                                               |
| :------------------------ | :--------------------------------------------------------------- | :-------------------------------------------------------------------------- |
| Cloudflare Image Resizing | [`provideCloudflareLoader`](/api/common/provideCloudflareLoader) | [Documentation](https://developers.cloudflare.com/images/image-resizing/)   |
| Cloudinary                | [`provideCloudinaryLoader`](/api/common/provideCloudinaryLoader) | [Documentation](https://cloudinary.com/documentation/resizing_and_cropping) |
| ImageKit                  | [`provideImageKitLoader`](/api/common/provideImageKitLoader)     | [Documentation](https://docs.imagekit.io/)                                  |
| Imgix                     | [`provideImgixLoader`](/api/common/provideImgixLoader)           | [Documentation](https://docs.imgix.com/)                                    |
| Netlify                   | [`provideNetlifyLoader`](/api/common/provideNetlifyLoader)       | [Documentation](https://docs.netlify.com/image-cdn/overview/)               |

To use the **generic loader** no additional code changes are necessary. This is the default behavior.

### [Built-in Loaders](#built-in-loaders)

To use an existing loader for a **third-party image service**, add the provider factory for your chosen service to the `providers` array. In the example below, the Imgix loader is used:

```
providers: [
  provideImgixLoader('https://my.base.url/'),
],
```

```
providers: [
  {
    provide: IMAGE_LOADER,
    useValue: (config: ImageLoaderConfig) => {
      return `https://example.com/images?src=${config.src}&width=${config.width}&height=${config.height}`;
    },
  },
],
```

```
<img
  ngSrc="my-image.jpg"
  width="400"
  height="300"
  [loaderParams]="{transform: 'e_grayscale,r_10'}"
/>
```

#### [Object format](#object-format)

Provide transformations as an object with key-value pairs.

```
<img
  ngSrc="my-image.jpg"
  width="400"
  height="300"
  [loaderParams]="{transform: {e: 'grayscale', r: 10}}"
/>
```

**NOTE:** The `transform` property is not supported by the Netlify loader, as Netlify's image CDN does not provide custom transformation parameters.

### [Example custom loader](#example-custom-loader)

The following shows an example of a custom loader function. This example function concatenates `src`, `width`, and `height`, and uses `loaderParams` to control a custom CDN feature for rounded corners:

```
const myCustomLoader = (config: ImageLoaderConfig) => {
  let url = `https://example.com/images/${config.src}?`;
  let queryParams = [];
  if (config.width) {
    queryParams.push(`w=${config.width}`);
  }
  if (config.height) {
    queryParams.push(`h=${config.height}`);
  }
  if (config.loaderParams?.roundedCorners) {
    queryParams.push('mask=corners&corner-radius=5');
  }
  return url + queryParams.join('&');
};
```

Note that in the above example, we've invented the 'roundedCorners' property name to control a feature of our custom loader. We could then use this feature when creating an image, as follows:

```
<img ngSrc="profile.jpg" width="300" height="300" [loaderParams]="{roundedCorners: true}" />
```

```
providers: [
  provideImgixLoader('https://my.base.url/'),
],
```

----
url: https://angular.dev/api/core/testing/DeferBlockFixture
----

```
    
      class DeferBlockFixture {}
    
    
```

Renders the specified state of the defer fixture.

@paramstate`DeferBlockState`

the defer state to render

@returns`Promise<void>`

Retrieves all nested child defer block fixtures in a given defer block.

@returns`Promise<DeferBlockFixture[]>`

Jump to details

----
url: https://angular.dev/api/forms/signals/MetadataSetterType
----

```
    
      type MetadataSetterType<TKey> = TKey extends MetadataKey<any, infer TWrite, any> ? TWrite : never
    
    
```

Jump to details

----
url: https://angular.dev/cli/generate/service-worker
----

```
ng generate service-worker [options]
```

Adds a service worker to your project. Service workers enable your application to work offline or on low-quality networks by caching assets and intercepting network requests. This schematic configures your project to use a service worker.

## [Options](#options)

`project`

The name of the project to add the service worker to. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`target`

The build target to apply the service worker to. This is typically `build`, indicating that the service worker should be generated during the standard build process.

Value Type`string`Default`build`

Jump to details

----
url: https://angular.dev/ai/agent-skills
----

Agent Skills are specialized, domain-specific instructions and capabilities designed for AI agents like Gemini CLI. These skills provide architectural guidance, generate idiomatic Angular code, and help scaffold new projects using modern best practices.

By using Agent Skills, you can ensure that the AI agent you are working with has the most up-to-date information about Angular's conventions, reactivity models (like Signals), and project structure.

## [Available Skills](#available-skills)

The Angular team maintains a collection of official skills that are regularly updated to stay in sync with the latest framework improvements.

| Skill                   | Description                                                                                                                                                                                                                                                                                       |
| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`angular-developer`** | Generates Angular code and provides architectural guidance. Useful for creating components, services, or obtaining best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling, testing, or CLI tooling. |
| **`angular-new-app`**   | Creates a new Angular app using the Angular CLI. Provides important guidelines for effectively setting up and structuring a modern Angular application.                                                                                                                                           |

## [Using Agent Skills](#using-agent-skills)

Agent Skills are designed to be used with agentic coding tools like [Gemini CLI](https://geminicli.com/docs/cli/skills/), [Antigravity](https://antigravity.google/docs/skills) and more. Activating a skill loads the specific instructions and resources needed for that task.

To use these skills in your own environment you may follow the instructions for your specific tool or use a community tool like [skills.sh](https://skills.sh/).

```
npx skills add https://github.com/angular/skills
```

----
url: https://angular.dev/api/router/ViewTransitionsFeatureOptions
----

```
    
      interface ViewTransitionsFeatureOptions {}
    
    
```

Skips the very first call to `startViewTransition`. This can be useful for disabling the animation during the application's initial loading phase.

A function to run after the `ViewTransition` is created.

This function is run in an injection context and can use [`inject`](/api/core/inject).

Jump to details

----
url: https://angular.dev/ai/ai-tutor
----

The Angular AI Tutor is designed to interactively guide you step-by-step through building a complete, modern Angular application from the ground up. You'll learn the latest patterns and best practices by building a real, tangible project: a **"Smart Recipe Box"** for creating and managing recipes.

Our goal is to foster critical thinking and help you retain what you learn. Instead of just giving you code, the tutor will explain concepts, show you examples, and then give you project-specific exercises to solve on your own.

## [Get Started](#get-started)

You can access the AI tutor via the [Angular MCP server](ai/mcp).

1. [Install](ai/mcp#get-started) the Angular MCP server
2. Create a new Angular project `ng new <project-name>`
3. Navigate to your new project (`cd <project-name>`) in an AI-powered editor or tool, like the [Gemini CLI](https://geminicli.com/)
4. Enter a prompt like `launch the Angular AI tutor`

## [Using the AI Tutor](#using-the-ai-tutor)

Each module begins with a brief concept explanation. If applicable, the tutor will present a code example to demonstrate the concept. The tutor will also provide an open-ended exercise to test your understanding. Finally, the tutor will check your work before moving onto the next module.

## [How It Works: The Learning Cycle](#how-it-works-the-learning-cycle)

For each new topic, you'll follow a learning loop that emphasizes critical thinking to help you better retain what you learn.

1. **Learn the Concept:** The tutor will briefly explain a core Angular feature and show you a generic code example to illustrate it.
2. **Apply Your Knowledge:** You'll immediately get a hands-on exercise. The tutor presents these exercises at a high level with objectives and expected outcomes, encouraging you to think through the solution yourself.
3. **Get Feedback & Support:** When you're ready, let the tutor know. It will **automatically read your project files** to verify your solution is correct. If you get stuck, you are in complete control. You can ask for a **"hint"** for more guidance, or get step-by-step instructions by typing **"detailed guide"** or **"step-by-step instructions."**

Once you've succeeded, the tutor will move directly to the next topic. You can also ask the tutor for more information on a topic or ask any related Angular questions at any time.

***

## [**Features & Commands**](#features--commands)

You are in control of your learning experience. Use these features at any time:

### [**Leave and Come Back**](#leave-and-come-back)

Feel free to take a break. Your progress is tied to your project's code. When you return for a new session, the tutor will automatically analyze your files to determine exactly where you left off, allowing you to seamlessly pick up right where you were.

**Pro Tip:** We highly recommend using Git to save your progress. After completing a module, it's a great idea to commit your changes (e.g., `git commit -m "Complete Phase 1, Module 8"`). This acts as a personal checkpoint you can always return to.

### [**Adjust Your Experience Level**](#adjust-your-experience-level)

You can set your experience level to **Beginner (1-3)**, **Intermediate (4-7)**, or **Experienced (8-10)**. You can change this setting at any time during your session, and the tutor will immediately adapt its teaching style to match.

**Example Prompts:**

* "Set my experience level to beginner."
* "Change my rating to 8."

### [**See the Full Learning Plan**](#see-the-full-learning-plan)

Want to see the big picture or check how far you've come? Just ask for the table of contents.

**Example Prompts:**

* "Where are we?"
* "Show the table of contents."
* "Show the plan."

The tutor will display the full learning plan and mark your current location.

### [**A Note on Styling**](#a-note-on-styling)

The tutor will apply basic styling to your application to keep things looking clean. You are highly encouraged to apply your own styling to make the app your own.

### [**Skip the Current Module**](#skip-the-current-module)

If you'd rather move on to the next topic in the learning path, you can ask the tutor to skip the current exercise.

**Example Prompts:**

* "Skip this section."
* "Auto-complete this step for me."

The tutor will ask for confirmation and then present you with the complete code solution for the current module and attempt to automatically apply any required updates to ensure you can continue smoothly with the next module.

### [**Jump to Any Topic**](#jump-to-any-topic)

If you want to learn about a specific topic out of order (e.g., jump from the basics to forms), you can. The tutor will provide the necessary code to update your project to the correct starting point for the selected module and attempt to automatically apply any required updates.

**Example Prompts:**

* "Take me to the forms lesson."
* "I want to learn about Route Guards now."
* "Jump to the section on Services."

***

## [**Troubleshooting**](#troubleshooting)

### [Setup Issues](#setup-issues)

**"launch the Angular AI tutor" doesn't do anything?**

Make sure you have a project open first. The tutor needs an actual Angular project to work with:

```
ng new my-app
cd my-app
code .
```

Then ensure your MCP server is running. In VS Code, open the `.vscode/mcp.json` file and click the **"Start"** button at the top of the file.

When you type "launch the Angular AI tutor", you should see a checkmark saying "Reviewed .vscode/mcp.json and ran start task" and a prompt asking to "Allow task run?" — go ahead and click Allow.

**Still not working?**

Try typing `#angular-cli` first to load the Angular context, then paste the tutorial URL: `https://angular.dev/ai/ai-tutor`

**How to verify the server is running**

Open the Command Palette (`Ctrl+Shift+P`), type "MCP: List Running Servers", and look for "angular-cli" in the list.

***

### [General Issues](#general-issues)

If the tutor doesn't respond correctly or you suspect an issue with your application, here are a few things to try:

1. **Type "proceed":** This can often nudge the tutor to continue to the next step in the event it gets stuck.
2. **Correct the Tutor:** If the tutor is mistaken about your progress (e.g., it says you're on Module 3 but you've completed Module 8), just tell it. For example: *"I'm actually on Module 8."* The tutor will re-evaluate your code and adjust.
3. **Verify Your UI:** If you want to confirm what your application's user interface should look like, just ask the tutor. For example: *"What should I see in my UI?"*
4. **Reload the Browser Window:** A refresh can solve many issues related to your application.
5. **Hard Restart the Browser:** Errors are sometimes only surfaced in the browser's developer console. A hard restart can help clear underlying issues related to the application.
6. **Start a New Chat:** You can always start a new chat to remove the existing history and begin fresh. The tutor will read your files to find the latest step you were on.

## [**Your Learning Journey: The Phased Path**](#your-learning-journey-the-phased-path)

You will build your application over a five-phase journey. You can follow this path from start to finish to create a complete, fully-functional Angular application. Each module builds logically upon the last, taking you from the basics to advanced, real-world features.

**A Note on Automated Setup:** Some modules require a setup step, like creating interfaces or mock data. In these cases, the tutor will present you with the code and file instructions. You will be responsible for creating and modifying these files as instructed before the exercise begins.

### [**Phase 1: Angular Fundamentals**](#phase-1-angular-fundamentals)

* **Module 1:** Getting Started
* **Module 2:** Dynamic Text with Interpolation
* **Module 3:** Event Listeners (`(click)`)

### [**Phase 2: State and Signals**](#phase-2-state-and-signals)

* **Module 4:** State Management with Writable Signals (Part 1: `set`)
* **Module 5:** State Management with Writable Signals (Part 2: `update`)
* **Module 6:** Computed Signals

### [**Phase 3: Component Architecture**](#phase-3-component-architecture)

* **Module 7:** Template Binding (Properties & Attributes)
* **Module 8:** Creating & Nesting Components
* **Module 9:** Component Inputs with Signals
* **Module 10:** Styling Components
* **Module 11:** List Rendering with `@for`
* **Module 12:** Conditional Rendering with `@if`

### [**Phase 4: Advanced Features & Architecture**](#phase-4-advanced-features--architecture)

* **Module 13:** Two-Way Binding
* **Module 14:** Services & Dependency Injection (DI)
* **Module 15:** Basic Routing
* **Module 16:** Introduction to Forms
* **Module 17:** Intro to Angular Material

### [**Phase 5: Signal Forms**](#phase-5-signal-forms)

* **Module 18**: **Introduction to Signal Forms**
* **Module 19**: **Submitting & Resetting**
* **Module 20**: **Validation in Signal Forms**
* **Module 21**: **Field State & Error Messages**

***

## [**A Note on AI & Feedback**](#a-note-on-ai--feedback)

This tutor is powered by a Large Language Model (LLM). While we've worked hard to make it an expert, AIs can make mistakes. If you encounter an explanation or code example that seems incorrect, please let us know. You can correct the tutor, and it will use your feedback to adjust its response.

For any technical bugs or feature requests, please [submit an issue](https://github.com/angular/angular-cli/issues).

----
url: https://angular.dev/api/cdk/drag-drop/DropListOrientation
----

```
    
      type DropListOrientation = 'horizontal' | 'vertical' | 'mixed'
    
    
```

Jump to details

----
url: https://angular.dev/api/core/TypeProvider
----

```
    
      interface TypeProvider extends Type<any> {}
    
    
```

Configures the [`Injector`](/api/core/Injector) to return an instance of [`Type`](/api/core/Type) when [`Type`](/api/core/Type) is used as the token.

Create an instance by invoking the `new` operator and supplying additional arguments. This form is a short form of [`TypeProvider`](/api/core/TypeProvider);

For more details, see the ["Dependency Injection Guide"](guide/di).

## [Usage Notes](#usage-notes)

```
@Injectable()
        class Greeting {
          salutation = 'Hello';
        }

        const injector = Injector.create({providers: [{provide: Greeting, useClass: Greeting}]});

        expect(injector.get(Greeting).salutation).toBe('Hello');
```

Jump to details

----
url: https://angular.dev/api/forms/CheckboxRequiredValidator
----

## [API](/api/forms/CheckboxRequiredValidator#api)

```
    
      class CheckboxRequiredValidator extends RequiredValidator {}
    
    
```

Tracks changes to the required attribute bound to this directive.

## [Description](/api/forms/CheckboxRequiredValidator#description)

A Directive that adds the `required` validator to checkbox controls marked with the `required` attribute. The directive is provided with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) multi-provider list.

## [Exported by](/api/forms/CheckboxRequiredValidator#exported-by)

* `FormsModule`
* `ReactiveFormsModule`

## [Usage Notes](/api/forms/CheckboxRequiredValidator#usage-notes)

### [Adding a required checkbox validator using template-driven forms](/api/forms/CheckboxRequiredValidator#adding-a-required-checkbox-validator-using-template-driven-forms)

The following example shows how to add a checkbox required validator to an input attached to an ngModel binding.

```
<input type="checkbox" name="active" ngModel required>
```

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/13-define-a-route
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/12-enable-routing)[](/tutorials/learn-angular/14-routerLink)

[](/tutorials/learn-angular/12-enable-routing)[](/tutorials/learn-angular/14-routerLink)

Now that you've set up the app to use Angular Router, you need to define the routes.

**NOTE:** Learn more about [defining a basic route in the in-depth guide](/guide/routing/define-routes).

In this activity, you'll learn how to add and configure routes with your app.

***

1. ### [Define a route in `app.routes.ts`](#define-a-route-in-approutests)

   In your app, there are two pages to display: (1) Home Page and (2) User Page.

   To define a route, add a route object to the `routes` array in `app.routes.ts` that contains:

   * The `path` of the route (which automatically starts at the root path (i.e., `/`))
   * The `component` that you want the route to display

   ```
   import {Routes} from '@angular/router';
   import {Home} from './home/home';

   export const routes: Routes = [
     {
       path: '',
       component: Home,
     },
   ];
   ```

   The code above is an example of how `Home` can be added as a route. Now go ahead and implement this along with the `User` in the playground.

   Use `'user'` for the path of `User`.

2. ### [Add title to route definition](#add-title-to-route-definition)

   In addition to defining the routes correctly, Angular Router also enables you to set the page title whenever users are navigating by adding the `title` property to each route.

   In `app.routes.ts`, add the `title` property to the default route (`path: ''`) and the `user` route. Here's an example:

   ```
   import {Routes} from '@angular/router';
   import {Home} from './home/home';

   export const routes: Routes = [
     {
       path: '',
       title: 'App Home Page',
       component: Home,
     },
   ];
   ```

In the activity, you've learned how to define and configure routes in your Angular app. Nice work. 🙌

The journey to fully enabling routing in your app is almost complete, keep going.

----
url: https://angular.dev/api/core/EffectCleanupFn
----

```
    
      type EffectCleanupFn = () => void
    
    
```

Jump to details

----
url: https://angular.dev/best-practices/a11y
----

The web is used by a wide variety of people, including those who have visual or motor impairments. A variety of assistive technologies are available that make it much easier for these groups to interact with web-based software applications. Also, designing an application to be more accessible generally improves the user experience for all users.

For an in-depth introduction to issues and techniques for designing accessible applications, see Google's web.dev [Learn Accessibility](https://web.dev/learn/accessibility/) course.

This page discusses best practices for designing Angular applications that work well for all users, including those who rely on assistive technologies.

## [Accessibility attributes](#accessibility-attributes)

Building accessible web experiences often involves setting [Accessible Rich Internet Applications (ARIA) attributes](https://web.dev/learn/accessibility/aria-html/) to provide semantic meaning where it might otherwise be missing. Use [attribute binding](guide/templates/binding#binding-dynamic-properties-and-attributes) template syntax to control the values of accessibility-related attributes.

### [ARIA attributes and properties](#aria-attributes-and-properties)

When binding to ARIA attributes in Angular, you can use them directly like any other HTML attribute.

```
<button [aria-label]="myActionLabel">…</button>
```

Static ARIA attributes work as regular HTML attributes.

```
<button aria-label="Save document">…</button>
```

**HELPFUL:** By convention, HTML attributes use lowercase names (`tabindex`), while properties use camelCase names (`tabIndex`).

Some ARIA patterns expose DOM APIs or directive inputs that accept structured values (for example, collections of `Element` references). Use standard property bindings for those cases so the underlying relationship stays synchronized.

```
@Component({
  template: `
    <h2 #dialogTitle>Attention</h2>
    <p #dialogDescription>Please review your answers before continuing.</p>

    <section role="dialog" [ariaLabelledByElements]="[dialogTitle, dialogDescription]">
      <ng-content />
    </section>
  `,
})
export class ReviewDialog {}
```

Here `[ariaLabelledByElements]` accepts an array of elements, so property binding keeps the element references up to date whenever the template data changes.

See the [binding guide](guide/templates/binding#aria-attributes) for a summary of ARIA attribute syntax.

## [Angular UI components](#angular-ui-components)

The [Angular Material](https://material.angular.dev) library, which is maintained by the Angular team, is a suite of reusable UI components that aims to be fully accessible. The [Component Development Kit (CDK)](https://material.angular.dev/cdk/categories) includes the `a11y` package that provides tools to support various areas of accessibility. For example:

* `LiveAnnouncer` is used to announce messages for screen-reader users using an `aria-live` region. See the W3C documentation for more information on [aria-live regions](https://www.w3.org/WAI/PF/aria-1.1/states_and_properties#aria-live).

* The `cdkTrapFocus` directive traps Tab-key focus within an element. Use it to create accessible experience for components such as modal dialogs, where focus must be constrained.

For full details of these and other tools, see the [Angular CDK accessibility overview](https://material.angular.dev/cdk/a11y/overview).

For custom-styled components that need reusable WAI-ARIA interaction patterns, [Angular Aria](guide/aria/overview) provides headless directives for patterns such as accordion, combobox, listbox, menu, tabs, and toolbar. These directives handle keyboard interaction, ARIA attributes, focus management, and screen reader support while letting you provide the HTML structure and styling for your application.

### [Augmenting native elements](#augmenting-native-elements)

Native HTML elements capture several standard interaction patterns that are important to accessibility. When authoring Angular components, you should re-use these native elements directly when possible, rather than re-implementing well-supported behaviors.

For example, instead of creating a custom element for a new variety of button, create a component that uses an attribute selector with a native `<button>` element. This most commonly applies to `<button>` and `<a>`, but can be used with many other types of element.

You can see examples of this pattern in Angular Material: [`MatButton`](https://github.com/angular/components/blob/main/src/material/button/button.ts#L33C3-L36C5), [`MatTabNav`](https://github.com/angular/components/blob/main/src/material/tabs/tab-nav-bar/tab-nav-bar.ts#L62), and [`MatTable`](https://github.com/angular/components/blob/main/src/material/table/table.ts#L40).

### [Using containers for native elements](#using-containers-for-native-elements)

Sometimes using the appropriate native element requires a container element. For example, the native `<input>` element cannot have children, so any custom text entry components need to wrap an `<input>` with extra elements. By just including `<input>` in your custom component's template, it's impossible for your component's users to set arbitrary properties and attributes to the `<input>` element. Instead, create a container component that uses content projection to include the native control in the component's API.

You can see [`MatFormField`](https://material.angular.dev/components/form-field/overview) as an example of this pattern.

## [Case study: Building a custom progress bar](#case-study-building-a-custom-progress-bar)

The following example shows how to make a progress bar accessible by using host binding to control accessibility-related attributes.

* The component defines an accessibility-enabled element with both the standard HTML attribute `role`, and ARIA attributes. The ARIA attribute `aria-valuenow` is bound to the user's input.
* In the template, the `aria-label` attribute ensures that the control is accessible to screen readers.

```
1import {Component, input} from '@angular/core';
2
3/**
4 * Example progressbar component.
5 */
6@Component({
7  selector: 'app-example-progressbar',
8  template: '<div class="bar" [style.width.%]="value()"></div>',
9  styleUrls: ['./progress-bar.component.css'],
10  host: {
11    // Sets the role for this component to "progressbar"
12    role: 'progressbar',
13
14    // Sets the minimum and maximum values for the progressbar role.
15    'aria-valuemin': '0',
16    'aria-valuemax': '100',
17
18    // Binding that updates the current value of the progressbar.
19    '[attr.aria-valuenow]': 'value()',
20  },
21})
22export class ExampleProgressbarComponent {
23  /** Current value of the progressbar. */
24  value = input(0);
25}
```

```
1<h1>Accessibility Example</h1>
2<label for="progress-value">
3  Enter an example progress value
4  <input
5    id="progress-value"
6    type="number"
7    min="0"
8    max="100"
9    [value]="progress"
10    (input)="setProgress($event)"
11  />
12</label>
13
14<!-- The user of the progressbar sets an aria-label to communicate what the progress means. -->
15<app-example-progressbar [value]="progress" aria-label="Example of a progress bar" />
```

## [Routing](#routing)

### [Focus management after navigation](#focus-management-after-navigation)

Tracking and controlling [focus](https://web.dev/learn/accessibility/focus/) in a UI is an important consideration in designing for accessibility. When using Angular routing, you should decide where page focus goes upon navigation.

To avoid relying solely on visual cues, you need to make sure your routing code updates focus after page navigation. Use the [`NavigationEnd`](/api/router/NavigationEnd) event from the [`Router`](/api/router/Router) service to know when to update focus.

The following example shows how to find and focus the main content header in the DOM after navigation.

```
router.events.pipe(filter((e) => e instanceof NavigationEnd)).subscribe(() => {
  const mainHeader = document.querySelector('#main-content-header');
  if (mainHeader) {
    mainHeader.focus();
  }
});
```

In a real application, the element that receives focus depends on your specific application structure and layout. The focused element should put users in a position to immediately move into the main content that has just been routed into view. You should avoid situations where focus returns to the `body` element after a route change.

### [Active links identification](#active-links-identification)

CSS classes applied to active [`RouterLink`](/api/router/RouterLink) elements, such as [`RouterLinkActive`](/api/router/RouterLinkActive), provide a visual cue to identify the active link. Unfortunately, a visual cue doesn't help blind or visually impaired users. Applying the `aria-current` attribute to the element can help identify the active link. For more information, see [Mozilla Developer Network (MDN) aria-current](https://developer.mozilla.org/docs/Web/Accessibility/ARIA/Attributes/aria-current)).

The [`RouterLinkActive`](/api/router/RouterLinkActive) directive provides the `ariaCurrentWhenActive` input which sets the `aria-current` to a specified value when the link becomes active.

The following example shows how to apply the `active-page` class to active links as well as setting their `aria-current` attribute to `"page"` when they are active:

```
<nav>
  <a routerLink="home" routerLinkActive="active-page" ariaCurrentWhenActive="page"> Home </a>
  <a routerLink="about" routerLinkActive="active-page" ariaCurrentWhenActive="page"> About </a>
  <a routerLink="shop" routerLinkActive="active-page" ariaCurrentWhenActive="page"> Shop </a>
</nav>
```

## [Deferred Loading](#deferred-loading)

When using Angular's `@defer` blocks for lazy loading content, consider the accessibility implications for users with assistive technologies. Screen readers may not automatically announce content changes when deferred components load, potentially leaving users unaware of new content.

To ensure deferred content changes are properly announced, wrap your `@defer` blocks in elements with appropriate ARIA live regions. For detailed guidance and examples, see the [accessibility section in the defer guide](/guide/templates/defer#keep-accessibility-in-mind).

## [More information](#more-information)

* [Accessibility - Google Web Fundamentals](https://developers.google.com/web/fundamentals/accessibility)
* [ARIA specification and authoring practices](https://www.w3.org/TR/wai-aria)
* [Material Design - Accessibility](https://material.io/design/usability/accessibility.html)
* [Smashing Magazine](https://www.smashingmagazine.com/search/?q=accessibility)
* [Inclusive Components](https://inclusive-components.design)
* [Accessibility Resources and Code Examples](https://dequeuniversity.com/resources)
* [W3C - Web Accessibility Initiative](https://www.w3.org/WAI/people-use-web)
* [Rob Dodson A11ycasts](https://www.youtube.com/watch?v=HtTyRajRuyY)
* [Angular ESLint](https://github.com/angular-eslint/angular-eslint#functionality) provides linting rules that can help you make sure your code meets accessibility standards.

Books

* "A Web for Everyone: Designing Accessible User Experiences," Sarah Horton and Whitney Quesenbery
* "Inclusive Design Patterns," Heydon Pickering

----
url: https://angular.dev/errors/NG01902
----

This error indicates that a [`Field`](/api/forms/signals/Field) instance is no longer connected to the current model structure. Angular signal forms throw `NG01902` when a field path that previously existed can no longer be resolved as the same property in the parent object.

A common trigger is unintentionally setting a field's value to `undefined`. In signal forms, `undefined` means "field does not exist", so an existing field can become orphaned.

## [Why this happens](#why-this-happens)

Signal forms keep field instances in sync with your model shape. If code still references an older field instance after the model shape changed, Angular detects the mismatch and throws this error.

This can happen when:

* a property is removed from the model object
* a property becomes `undefined` instead of remaining present
* code holds a stale field reference across structural updates

## [How to fix it](#how-to-fix-it)

* Avoid writing `undefined` to model fields. Use `null` or another explicit value when the field should still exist.
* Keep the model shape stable while a field is in use.
* Re-read fields from the current form tree after structural updates instead of reusing stale references.

For model design guidance, see [Avoid `undefined`](/guide/forms/signals/model-design#avoid-undefined).

----
url: https://angular.dev/extended-diagnostics/NG8103
----

This diagnostics ensures that a standalone component which uses known control flow directives (such as `*ngIf`, `*ngFor`, `*ngSwitchCase`, or `*ngSwitchDefault`) in a template, also imports those directives either individually or by importing the [`CommonModule`](/api/common/CommonModule).

```
import {Component} from '@angular/core';

@Component({
  // Template uses `*ngIf`, but no corresponding directive imported.
  imports: [],
  template: `<div *ngIf="visible">Hi</div>`,
})
class MyComponent {}
```

## [What's wrong with that?](#whats-wrong-with-that)

Using a control flow directive without importing it will fail at runtime, as Angular attempts to bind to an [`ngIf`](/api/common/NgIf) property of the HTML element, which does not exist.

## [What should I do instead?](#what-should-i-do-instead)

Make sure that a corresponding control flow directive is imported.

A directive can be imported individually:

```
import {Component} from '@angular/core';
import {NgIf} from '@angular/common';

@Component({
  imports: [NgIf],
  template: `<div *ngIf="visible">Hi</div>`,
})
class MyComponent {}
```

or you could import the entire [`CommonModule`](/api/common/CommonModule), which contains all control flow directives:

```
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';

@Component({
  imports: [CommonModule],
  template: `<div *ngIf="visible">Hi</div>`,
})
class MyComponent {}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `missingControlFlowDirective` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "missingControlFlowDirective": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/errors/NG0505
----

This error occurs when hydration is enabled on the client, but the server response doesn't contain special serialized information about the application that hydration logic relies on.

This can happen when the [`provideClientHydration()`](/api/platform-browser/provideClientHydration) function is included in the client part of the application configuration, but is missing in the server part of the configuration.

In applications with the default project structure (generated by the `ng new`), the [`provideClientHydration()`](/api/platform-browser/provideClientHydration) call is added either into the `providers` array of the main `AppModule` (which is imported into the server module) or into a set of providers that are included into both client and server configurations.

If you have a custom setup where client and server configuration are independent and do not share common set of providers, make sure that the [`provideClientHydration()`](/api/platform-browser/provideClientHydration) is also included into the set of providers used to bootstrap an application on the server.

More information about hydration can be found in [this guide](guide/hydration).

## [Debugging the error](#debugging-the-error)

Verify that the [`provideClientHydration()`](/api/platform-browser/provideClientHydration) call is included into a set of providers that is used to bootstrap an application on the server.

----
url: https://angular.dev/extended-diagnostics/NG8106
----

This diagnostic detects when the `.px`, `.%`, and `.em` suffixes are used with an attribute binding.

```
<img [attr.width.px]="5" />
```

## [What's wrong with that?](/extended-diagnostics/NG8106#whats-wrong-with-that)

These suffixes are only available for style bindings. They do not have any meaning when binding to an attribute.

## [What should I do instead?](/extended-diagnostics/NG8106#what-should-i-do-instead)

Rather than using the `.px`, `.%`, or `.em` suffixes that are only supported in style bindings, move this to the value assignment of the binding.

```
<img [attr.width]="'5px'" />
```

## [Configuration requirements](/extended-diagnostics/NG8106#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `suffixNotSupported` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](/extended-diagnostics/NG8106#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "suffixNotSupported": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/animations/AnimationPlayer
----

```
    
      interface AnimationPlayer {}
    
    
```

Provides a callback to invoke when the animation finishes.

@paramfn`() => void`

The callback function.

@returns`void`

Provides a callback to invoke when the animation starts.

@paramfn`() => void`

The callback function.

@returns`void`

Provides a callback to invoke after the animation is destroyed.

@paramfn`() => void`

The callback function.

@returns`void`

Initializes the animation.

@returns`void`

Reports whether the animation has started.

@returns`boolean`

True if the animation has started, false otherwise.

Runs the animation, invoking the `onStart()` callback.

@returns`void`

Pauses the animation.

@returns`void`

Restarts the paused animation.

@returns`void`

Ends the animation, invoking the `onDone()` callback.

@returns`void`

Destroys the animation, after invoking the `beforeDestroy()` callback. Calls the `onDestroy()` callback when destruction is completed.

@returns`void`

Resets the animation to its initial state.

@returns`void`

Sets the position of the animation.

@paramposition`number`

A fractional value, representing the progress through the animation.

@returns`void`

Reports the current position of the animation.

@returns`number`

A fractional value, representing the progress through the animation.

The parent of this player, if any.

The total run time of the animation, in milliseconds.

Provides a callback to invoke before the animation is destroyed.

Jump to details

----
url: https://angular.dev/api/common/AsyncPipe
----

## [Pipe usage](/api/common/AsyncPipe#pipe-usage)

```
{{ value_expression | async }}
```

## [API](/api/common/AsyncPipe#api)

```
    
      class AsyncPipe implements OnDestroy ,PipeTransform {
}
    
    
```

@paramref`ChangeDetectorRef`

@returns`AsyncPipe`

@returns`void`

@paramobj`any`

@returns`T | null`

@paramobj`null | undefined`

@returns`null`

@paramobj`any`

@returns`T | null`

## [Description](/api/common/AsyncPipe#description)

Unwraps a value from an asynchronous primitive.

The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has emitted. When a new value is emitted, the `async` pipe marks the component to be checked for changes. When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid potential memory leaks. When the reference of the expression changes, the `async` pipe automatically unsubscribes from the old `Observable` or `Promise` and subscribes to the new one.

## [Exported by](/api/common/AsyncPipe#exported-by)

* `CommonModule`

## [Usage Notes](/api/common/AsyncPipe#usage-notes)

### [Examples](/api/common/AsyncPipe#examples)

This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the promise.

```
@Component({
  selector: 'async-promise-pipe',
  imports: [AsyncPipe],
  template: `<div>
    <code>promise|async</code>:
    <button (click)="clicked()">{{ arrived ? 'Reset' : 'Resolve' }}</button>
    <span>Wait for it... {{ greeting | async }}</span>
  </div>`,
})
export class AsyncPromisePipeComponent {
  greeting: Promise<string> | null = null;
  arrived: boolean = false;

  private resolve: Function | null = null;

  constructor() {
    this.reset();
  }

  reset() {
    this.arrived = false;
    this.greeting = new Promise<string>((resolve, reject) => {
      this.resolve = resolve;
    });
  }

  clicked() {
    if (this.arrived) {
      this.reset();
    } else {
      this.resolve!('hi there!');
      this.arrived = true;
    }
  }
}
```

It's also possible to use `async` with Observables. The example below binds the `time` Observable to the view. The Observable continuously updates the view with the current time.

```
@Component({
  selector: 'async-observable-pipe',
  imports: [AsyncPipe],
  template: '<div><code>observable|async</code>: Time: {{ time | async }}</div>',
})
export class AsyncObservablePipeComponent {
  time = new Observable<string>((observer: Observer<string>) => {
    setInterval(() => observer.next(new Date().toString()), 1000);
  });
}
```

Jump to details

----
url: https://angular.dev/api/aria/menu/MenuBar
----

```
    
      class MenuBar<V> implements OnDestroy {}
    
    
```

A reference to the host element.

Whether the menubar is disabled.

Whether the menubar is soft disabled.

The directionality (LTR / RTL) context for the application (or a subtree of it).

The values of the currently selected menu items.

Whether the menu should wrap its items.

The delay in milliseconds before the typeahead buffer is cleared.

A callback function triggered when a menu item is selected.

@returns`void`

Closes the menubar.

@returns`void`

## [Description](#description)

A menu bar of menu items.

Like the `ngMenu`, a `ngMenuBar` is used to offer a list of menu item choices to users. However, a menubar is used to display a persistent, top-level, always-visible set of menu item choices, typically found at the top of an application window.

```
<div ngMenuBar>
  <button ngMenuTrigger [menu]="fileMenu">File</button>
  <button ngMenuTrigger [menu]="editMenu">Edit</button>
</div>

<div ngMenu #fileMenu="ngMenu">
  <div ngMenuItem value="New">New</div>
  <div ngMenuItem value="Open">Open</div>
</div>

<div ngMenu #editMenu="ngMenu">
  <div ngMenuItem value="Cut">Cut</div>
  <div ngMenuItem value="Copy">Copy</div>
</div>
```

Jump to details

----
url: https://angular.dev/api/common/NgOptimizedImage
----

```
    
      class NgOptimizedImage implements OnInit ,OnChanges {}
    
    
```

Name of the source image. Image name will be processed by the image loader and the final URL will be applied as the `src` property of the image.

A comma separated list of width or density descriptors. The image name will be taken from `ngSrc` and combined with the list of width or density descriptors to generate the final `srcset` property of the image.

Example:

```
<img ngSrc="hello.jpg" ngSrcset="100w, 200w" />  =>
<img src="path/hello.jpg" srcset="path/hello.jpg?w=100 100w, path/hello.jpg?w=200 200w" />
```

The base `sizes` attribute passed through to the `<img>` element. Providing sizes causes the image to create an automatic responsive srcset.

For responsive images: the intrinsic width of the image in pixels. For fixed size images: the desired rendered width of the image in pixels.

For responsive images: the intrinsic height of the image in pixels. For fixed size images: the desired rendered height of the image in pixels.

The desired decoding behavior for the image. Defaults to `auto` if not explicitly set, matching native browser behavior.

Use `async` to decode the image off the main thread (non-blocking), `sync` for immediate decoding (blocking), or `auto` to let the browser decide the optimal strategy.

[Spec](https://html.spec.whatwg.org/multipage/images.html#image-decoding-hint)

The desired loading behavior (lazy, eager, or auto). Defaults to `lazy`, which is recommended for most images.

Warning: Setting images as loading="eager" or loading="auto" marks them as non-priority images and can hurt loading performance. For images which may be the LCP element, use the `priority` attribute instead of `loading`.

Indicates whether this image should have a high priority.

Data to pass through to custom loaders.

Disables automatic srcset generation for this image.

Sets the image to "fill mode", which eliminates the height/width requirement and adds styles such that the image fills its containing element.

A URL or data URL for an image to be used as a placeholder while this image loads.

Configuration object for placeholder settings. Options:

* blur: Setting this to false disables the automatic CSS blur.

Returns an image url formatted for use with the CSS background-image property. Expects one of:

* A base64 encoded image, which is wrapped and passed through.
* A boolean. If true, calls the image loader to generate a small placeholder url.

@paramplaceholderInput`string | boolean`

@returns`string | boolean | null`

Determines if blur should be applied, based on an optional boolean property `blur` within the optional configuration object `placeholderConfig`.

@paramplaceholderConfig`ImagePlaceholderConfig | undefined`

@returns`boolean`

## [Description](#description)

Directive that improves image loading performance by enforcing best practices.

[`NgOptimizedImage`](/api/common/NgOptimizedImage) ensures that the loading of the Largest Contentful Paint (LCP) image is prioritized by:

* Automatically setting the `fetchpriority` attribute on the `<img>` tag
* Lazy loading non-priority images by default
* Automatically generating a preconnect link tag in the document head

In addition, the directive:

* Generates appropriate asset URLs if a corresponding [`ImageLoader`](/api/common/ImageLoader) function is provided
* Automatically generates a srcset
* Requires that `width` and `height` are set
* Warns if `width` or `height` have been set incorrectly
* Warns if the image will be visually distorted when rendered

## [Usage Notes](#usage-notes)

Follow the steps below to enable and use the directive:

1. Import it into a Component.
2. Optionally provide an [`ImageLoader`](/api/common/ImageLoader) if you use an image hosting service.
3. Update the necessary `<img>` tags in templates and replace `src` attributes with `ngSrc`. Using a `ngSrc` allows the directive to control when the `src` gets set, which triggers an image download.

Step 1: import the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive.

```
@Component({
  imports: [NgOptimizedImage],
})
class MyPage {}
```

Step 2: configure a loader.

To use the **default loader**: no additional code changes are necessary. The URL returned by the generic loader will always match the value of "src". In other words, this loader applies no transformations to the resource URL and the value of the `ngSrc` attribute will be used as is.

To use an existing loader for a **third-party image service**: add the provider factory for your chosen service to the `providers` array. In the example below, the Imgix loader is used:

```
import {provideImgixLoader} from '@angular/common';

// Call the function and add the result to the `providers` array:
providers: [
  provideImgixLoader("https://my.base.url/"),
],
```

The [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive provides the following functions:

* [`provideCloudflareLoader`](/api/common/provideCloudflareLoader)
* [`provideCloudinaryLoader`](/api/common/provideCloudinaryLoader)
* [`provideImageKitLoader`](/api/common/provideImageKitLoader)
* [`provideImgixLoader`](/api/common/provideImgixLoader)

If you use a different image provider, you can create a custom loader function as described below.

To use a **custom loader**: provide your loader function as a value for the [`IMAGE_LOADER`](/api/common/IMAGE_LOADER) DI token.

```
import {IMAGE_LOADER, ImageLoaderConfig} from '@angular/common';

// Configure the loader using the `IMAGE_LOADER` token.
providers: [
  {
     provide: IMAGE_LOADER,
     useValue: (config: ImageLoaderConfig) => {
       return `https://example.com/${config.src}-${config.width}.jpg`;
     }
  },
],
```

Step 3: update `<img>` tags in templates to use `ngSrc` instead of `src`.

```
<img ngSrc="logo.png" width="200" height="100">
```

Jump to details

----
url: https://angular.dev/api/platform-browser/testing/platformBrowserTesting
----

```
    
      const platformBrowserTesting: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
    
    
```

Jump to details

----
url: https://angular.dev/guide/i18n/manage-marked-text
----

The Angular extractor generates a file with a translation unit entry each of the following instances.

* Each `i18n` attribute in a component template
* Each [`$localize`](api/localize/init/$localize "$localize | init - localize - API | Angular") tagged message string in component code

As described in [How meanings control text extraction and merges](guide/i18n/prepare#h1-example "How meanings control text extraction and merges - Prepare components for translations | Angular"), Angular assigns each translation unit a unique ID.

The following example displays translation units with unique IDs.

### messages.fr.xlf

```
<trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
```

When you change the translatable text, the extractor generates a new ID for that translation unit. In most cases, changes in the source text also require a change to the translation. Therefore, using a new ID keeps the text change in sync with translations.

However, some translation systems require a specific form or syntax for the ID. To address the requirement, use a custom ID to mark text. Most developers don't need to use a custom ID. If you want to use a unique syntax to convey additional metadata, use a custom ID. Additional metadata may include the library, component, or area of the application in which the text appears.

To specify a custom ID in the `i18n` attribute or [`$localize`](api/localize/init/$localize "$localize | init - localize - API | Angular") tagged message string, use the `@@` prefix. The following example defines the `introductionHeader` custom ID in a heading element.

### app.component.html

```
<h1 i18n="@@introductionHeader">Hello i18n!</h1>
```

The following example defines the `introductionHeader` custom ID for a variable.

```
variableText1 = $localize`:@@introductionHeader:Hello i18n!`;
```

When you specify a custom ID, the extractor generates a translation unit with the custom ID.

### messages.fr.xlf

```
<trans-unit id="introductionHeader" datatype="html">
```

If you change the text, the extractor does not change the ID. As a result, you don't have to take the extra step to update the translation. The drawback of using custom IDs is that if you change the text, your translation may be out-of-sync with the newly changed source text.

## [Use a custom ID with a description](#use-a-custom-id-with-a-description)

Use a custom ID in combination with a description and a meaning to further help the translator.

The following example includes a description, followed by the custom ID.

### app.component.html

```
<h1 i18n="An introduction header for this sample@@introductionHeader">Hello i18n!</h1>
```

The following example defines the `introductionHeader` custom ID and description for a variable.

```
variableText2 = $localize`:An introduction header for this sample@@introductionHeader:Hello i18n!`;
```

The following example adds a meaning.

### app.component.html

```
<h1 i18n="site header|An introduction header for this sample@@introductionHeader">Hello i18n!</h1>
```

The following example defines the `introductionHeader` custom ID for a variable.

```
variableText3 = $localize`:site header|An introduction header for this sample@@introductionHeader:Hello i18n!`;
```

### [Define unique custom IDs](#define-unique-custom-ids)

Be sure to define custom IDs that are unique. If you use the same ID for two different text elements, the extraction tool extracts only the first one, and Angular uses the translation in place of both original text elements.

For example, in the following code snippet the same `myId` custom ID is defined for two different text elements.

### app.component.html

```
<h3 i18n="@@myId">Hello</h3>
<!-- ... -->
<p i18n="@@myId">Good bye</p>
```

The following displays the translation in French.

### src/locale/messages.fr.xlf

```
<trans-unit id="myId" datatype="html">
  <source>Hello</source>
  <target state="new">Bonjour</target>
</trans-unit>
```

Both elements now use the same translation (`Bonjour`), because both were defined with the same custom ID.

```
<h3>Bonjour</h3>
<!-- ... -->
<p>Bonjour</p>
```

----
url: https://angular.dev/errors/NG0401
----

Angular requires a platform to be initialized. This error is thrown when [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) is used on the server without providing a [`BootstrapContext`](/api/platform-browser/BootstrapContext).

When using server-side rendering, [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) requires a [`BootstrapContext`](/api/platform-browser/BootstrapContext) to be passed as an argument.

## [Debugging the error](#debugging-the-error)

To resolve this error, ensure that your `main.server.ts` file correctly passes a [`BootstrapContext`](/api/platform-browser/BootstrapContext) to the [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) function.

```
import {bootstrapApplication, BootstrapContext} from '@angular/platform-browser';
import {App} from './app/app';
import {config} from './app/app.config.server';

const bootstrap = (context: BootstrapContext) => bootstrapApplication(App, config, context);
export default bootstrap;
```

----
url: https://angular.dev/tools/cli
----

The Angular CLI is a command-line interface tool which allows you to scaffold, develop, test, deploy, and maintain Angular applications directly from a command shell.

Angular CLI is published on npm as the `@angular/cli` package and includes a binary named `ng`. Commands invoking `ng` are using the Angular CLI.

### Try Angular without local setup

If you are new to Angular, you might want to start with [Try it now!](tutorials/learn-angular), which introduces the essentials of Angular in the context of a ready-made basic online store app for you to examine and modify. This standalone tutorial takes advantage of the interactive [StackBlitz](https://stackblitz.com) environment for online development. You don't need to set up your local environment until you're ready.

### [Getting Started](tools/cli/setup-local)

[Install Angular CLI to create and build your first app.](tools/cli/setup-local)

[Get Started](tools/cli/setup-local)

### [Command Reference](cli)

[Discover CLI commands to make you more productive with Angular.](cli)

[Learn More](cli)

### [Schematics](tools/cli/schematics)

[Create and run schematics to generate and modify source files in your application automatically.](tools/cli/schematics)

[Learn More](tools/cli/schematics)

### [Builders](tools/cli/cli-builder)

[Create and run builders to perform complex transformations from your source code to generated build outputs.](tools/cli/cli-builder)

[Learn More](tools/cli/cli-builder)

## [CLI command-language syntax](#cli-command-language-syntax)

Angular CLI roughly follows Unix/POSIX conventions for option syntax.

### [Boolean options](#boolean-options)

Boolean options have two forms: `--this-option` sets the flag to `true`, `--no-this-option` sets it to `false`. You can also use `--this-option=false` or `--this-option=true`. If neither option is supplied, the flag remains in its default state, as listed in the reference documentation.

### [Array options](#array-options)

Array options can be provided in two forms: `--option value1 value2` or `--option value1 --option value2`.

### [Key/value options](#key-value-options)

Some options like `--define` expect an array of `key=value` pairs as their values. Just like array options, key/value options can be provided in two forms: `--define 'KEY_1="value1"' KEY_2=true` or `--define 'KEY_1="value1"' --define KEY_2=true`.

### [Relative paths](#relative-paths)

Options that specify files can be given as absolute paths, or as paths relative to the current working directory, which is generally either the workspace or project root.

----
url: https://angular.dev/cli/cache/info
----

```
ng cache info
```

Prints persistent disk cache configuration and statistics in the console.

Jump to details

----
url: https://angular.dev/api/aria/menu/Menu
----

```
    
      class Menu<V> implements OnDestroy {}
    
    
```

A reference to the host element.

The directionality (LTR / RTL) context for the application (or a subtree of it).

The unique ID of the menu.

Whether the menu should wrap its items.

The delay in milliseconds before the typeahead buffer is cleared.

Whether the menu is disabled.

A reference to the parent menu item or menu trigger.

Whether the menu is soft disabled.

Whether the menu is visible.

The tab index of the menu.

A callback function triggered when a menu item is selected.

The delay in milliseconds before expanding sub-menus on hover.

@returns`void`

Closes the menu.

@returns`void`

## [Description](#description)

A list of menu items.

A `ngMenu` is used to offer a list of menu item choices to users. Menus can be nested within other menus to create sub-menus. It works in conjunction with `ngMenuTrigger` and `ngMenuItem` directives.

```
<button ngMenuTrigger [menu]="myMenu">Options</button>

<div ngMenu #myMenu="ngMenu">
  <div ngMenuItem value="Star">Star</div>
  <div ngMenuItem value="Edit">Edit</div>
  <div ngMenuItem value="More" [submenu]="subMenu">More</div>
</div>

<div ngMenu #subMenu="ngMenu">
  <div ngMenuItem value="Sub Item 1">Sub Item 1</div>
  <div ngMenuItem value="Sub Item 2">Sub Item 2</div>
</div>
```

Jump to details

----
url: https://angular.dev/api/core/ResourceSnapshot
----

```
    
      type ResourceSnapshot<T> = | {readonly status: 'idle'; readonly value: T}  | {readonly status: 'loading' | 'reloading'; readonly value: T}  | {readonly status: 'resolved' | 'local'; readonly value: T}  | {readonly status: 'error'; readonly error: Error}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/DebugElement
----

```
    
      class DebugElement extends DebugNode {}
    
    
```

@paramnativeNode`Element`

@returns`DebugElement`

The underlying DOM element at the root of the component.

The element tag name, if it is an element.

Gets a map of property names to property values for an element.

This map includes:

* Regular property bindings (e.g. `[id]="id"`)
* Host property bindings (e.g. `host: { '[id]': "id" }`)
* Interpolated property bindings (e.g. \`id="{{ value }}")

It does not include:

* input property bindings (e.g. `[myCustomInput]="value"`)
* attribute bindings (e.g. `[attr.role]="menu"`)

A map of attribute names to attribute values for an element.

The inline styles of the DOM element.

A map containing the class names on the element as keys.

This map is derived from the `className` property of the DOM element.

Note: The values of this object will always be `true`. The class key will not appear in the KV object if it does not exist on the element.

The `childNodes` of the DOM element as a [`DebugNode`](/api/core/DebugNode) array.

The immediate [`DebugElement`](/api/core/DebugElement) children. Walk the tree by descending through `children`.

@parampredicate`Predicate<DebugElement>`

@returns`DebugElement`

the first [`DebugElement`](/api/core/DebugElement) that matches the predicate at any depth in the subtree.

@parampredicate`Predicate<DebugElement>`

@returns`DebugElement[]`

All [`DebugElement`](/api/core/DebugElement) matches for the predicate at any depth in the subtree.

@parampredicate`Predicate<DebugNode>`

@returns`DebugNode[]`

All [`DebugNode`](/api/core/DebugNode) matches for the predicate at any depth in the subtree.

Triggers the event by its name if there is a corresponding listener in the element's `listeners` collection.

If the event lacks a listener or there's some other problem, consider calling `nativeElement.dispatchEvent(eventObject)`.

@parameventName`string`

The name of the event to trigger

@parameventObj`any`

The *event object* expected by the handler

@returns`void`

----
url: https://angular.dev/api/forms/AsyncValidator
----

```
    
      interface AsyncValidator extends Validator {}
    
    
```

Method that performs async validation against the provided control.

@paramcontrol`AbstractControl<any, any, any>`

The control to validate against.

@returns`Promise<ValidationErrors | null> | Observable<ValidationErrors | null>`

A promise or observable that resolves a map of validation errors if validation fails, otherwise null.

Registers a callback function to call when the validator inputs change.

@paramfn`() => void`

The callback function

@returns`void`

## [Usage Notes](#usage-notes)

### [Provide a custom async validator directive](#provide-a-custom-async-validator-directive)

The following example implements the [`AsyncValidator`](/api/forms/AsyncValidator) interface to create an async validator directive with a custom error key.

```
import { of } from 'rxjs';

@Directive({
  selector: '[customAsyncValidator]',
  providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: CustomAsyncValidatorDirective, multi:
true}]
})
class CustomAsyncValidatorDirective implements AsyncValidator {
  validate(control: AbstractControl): Observable<ValidationErrors|null> {
    return of({'custom': true});
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/ApplicationConfig
----

```
    
      interface ApplicationConfig {}
    
    
```

List of providers that should be available to the root component and all its children.

Jump to details

----
url: https://angular.dev/api/core/PromiseResourceOptions
----

```
    
      interface PromiseResourceOptions<T, R> extends BaseResourceOptions<T, R> {}
    
    
```

Loading function which returns a `Promise` of the resource's value for a given request.

----
url: https://angular.dev/api/animations/AnimationAnimateMetadata
----

```
    
      interface AnimationAnimateMetadata extends AnimationMetadata {}
    
    
```

The timing data for the step.

A set of styles used in the step.

Jump to details

----
url: https://angular.dev/api/common/LocationChangeListener
----

## [API](/api/common/LocationChangeListener#api)

```
    
      interface LocationChangeListener {  (event: LocationChangeEvent): any;}
    
    
```

@paramevent`LocationChangeEvent`

@returns`any`

Jump to details

----
url: https://angular.dev/api/forms/MaxValidator
----

```
    
      class MaxValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the max bound to this directive.

Determines whether this validator should be active or not based on an input. Base class implementation checks whether an input is defined (if the value is different from `null` and `undefined`). Validator classes that extend this base class can override this function with the logic specific to a particular validator directive.

@paraminput`unknown`

@returns`boolean`

## [Description](#description)

A directive which installs the [`MaxValidator`](/api/forms/MaxValidator) for any `formControlName`, `formControl`, or control with `ngModel` that also has a `max` attribute.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Adding a max validator](#adding-a-max-validator)

The following example shows how to add a max validator to an input attached to an ngModel binding.

```
<input type="number" ngModel max="4">
```

Jump to details

----
url: https://angular.dev/api/platform-browser/HydrationFeature
----

```
    
      interface HydrationFeature<FeatureKind extends HydrationFeatureKind> {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/PipeTransform
----

```
    
      interface PipeTransform {}
    
    
```

@paramvalue`any`

@paramargs`any[]`

@returns`any`

## [Usage Notes](#usage-notes)

In the following example, `TruncatePipe` returns the shortened value with an added ellipses.

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
  transform(value: string) {
    return value.split(' ').slice(0, 2).join(' ') + '...';
  }
}
```

Invoking `{{ 'It was the best of times' | truncate }}` in a template will produce `It was...`.

In the following example, `TruncatePipe` takes parameters that sets the truncated length and the string to append with.

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'truncate',
})
export class TruncatePipe implements PipeTransform {
  transform(value: string, length: number, symbol: string) {
    return value.split(' ').slice(0, length).join(' ') + symbol;
  }
}
```

Invoking `{{ 'It was the best of times' | truncate:4:'....' }}` in a template will produce `It was the best....`.

Jump to details

----
url: https://angular.dev/api/forms/signals/FormRoot
----

```
    
      class FormRoot<T> {}
    
    
```

@paramevent`Event`

@returns`void`

## [Description](#description)

A directive that binds a [`FieldTree`](/api/forms/signals/FieldTree) to a `<form>` element.

It automatically:

1. Sets `novalidate` on the form element to disable browser validation.
2. Listens for the [`submit`](/api/forms/signals/submit) event, prevents the default behavior, and calls [`submit()`](/api/forms/signals/submit) on the [`FieldTree`](/api/forms/signals/FieldTree) if it defines its own submission options.

## [Usage Notes](#usage-notes)

```
<form [formRoot]="myFieldTree">
  ...
</form>
```

Jump to details

----
url: https://angular.dev/api/forms/ControlEvent
----

```
    
      abstract class ControlEvent<T = any> {}
    
    
```

Form control from which this event is originated.

Note: the type of the control can't be infered from T as the event can be emitted by any of child controls

Jump to details

----
url: https://angular.dev/api/forms/SelectControlValueAccessor
----

## [API](/api/forms/SelectControlValueAccessor#api)

```
    
      class SelectControlValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor {}
    
    
```

Tracks the option comparison algorithm for tracking identities when checking for changes.

## [Description](/api/forms/SelectControlValueAccessor#description)

The [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing select control values and listening to select control changes. The value accessor is used by the [`FormControlDirective`](/api/forms/FormControlDirective), [`FormControlName`](/api/forms/FormControlName), and [`NgModel`](/api/forms/NgModel) directives.

## [Exported by](/api/forms/SelectControlValueAccessor#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](/api/forms/SelectControlValueAccessor#usage-notes)

### [Using select controls in a reactive form](/api/forms/SelectControlValueAccessor#using-select-controls-in-a-reactive-form)

The following examples show how to use a select control in a reactive form.

```
import {Component} from '@angular/core';
import {FormControl, FormGroup} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form">
      <select formControlName="state">
        @for (state of states; track $index) {
          <option [ngValue]="state">{{ state.abbrev }}</option>
        }
      </select>
    </form>

    <p>Form value: {{ form.value | json }}</p>
    <!-- {state: {name: 'New York', abbrev: 'NY'} } -->
  `,
  standalone: false,
})
export class ReactiveSelectComp {
  states = [
    {name: 'Arizona', abbrev: 'AZ'},
    {name: 'California', abbrev: 'CA'},
    {name: 'Colorado', abbrev: 'CO'},
    {name: 'New York', abbrev: 'NY'},
    {name: 'Pennsylvania', abbrev: 'PA'},
  ];

  form = new FormGroup({
    state: new FormControl(this.states[3]),
  });
}
```

### [Using select controls in a template-driven form](/api/forms/SelectControlValueAccessor#using-select-controls-in-a-template-driven-form)

To use a select in a template-driven form, simply add an `ngModel` and a `name` attribute to the main `<select>` tag.

```
import {Component} from '@angular/core';

@Component({
  selector: 'example-app',
  template: `
    <form #f="ngForm">
      <select name="state" ngModel>
        <option value="" disabled>Choose a state</option>
        @for (state of states; track $index) {
          <option [ngValue]="state">{{ state.abbrev }}</option>
        }
      </select>
    </form>

    <p>Form value: {{ f.value | json }}</p>
    <!-- example value: {state: {name: 'New York', abbrev: 'NY'} } -->
  `,
  standalone: false,
})
export class SelectControlComp {
  states = [
    {name: 'Arizona', abbrev: 'AZ'},
    {name: 'California', abbrev: 'CA'},
    {name: 'Colorado', abbrev: 'CO'},
    {name: 'New York', abbrev: 'NY'},
    {name: 'Pennsylvania', abbrev: 'PA'},
  ];
}
```

### [Customizing option selection](/api/forms/SelectControlValueAccessor#customizing-option-selection)

Angular uses object identity to select option. It's possible for the identities of items to change while the data does not. This can happen, for example, if the items are produced from an RPC to the server, and that RPC is re-run. Even if the data hasn't changed, the second response will produce objects with different identities.

To customize the default option comparison algorithm, `<select>` supports `compareWith` input. `compareWith` takes a **function** which has two arguments: `option1` and `option2`. If `compareWith` is given, Angular selects option by the return value of the function.

```
const selectedCountriesControl = new FormControl();
```

```
<select [compareWith]="compareFn"  [formControl]="selectedCountriesControl">
   @for(country of countries; track $index) {
       <option[ngValue]="country">{{country.name}}</option>
   }
</select>

compareFn(c1: Country, c2: Country): boolean {
    return c1 && c2 ? c1.id === c2.id : c1 === c2;
}
```

**Note:** We listen to the 'change' event because 'input' events aren't fired for selects in IE, see: <https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event#browser_compatibility>

Jump to details

----
url: https://angular.dev/api/common/upgrade/AngularJSUrlCodec
----

```
    
      class AngularJSUrlCodec implements UrlCodec {
}
    
    
```

@parampath`string`

@returns`string`

@paramsearch`string | { [k: string]: unknown; }`

@returns`string`

@paramhash`string`

@returns`string`

@parampath`string`

@paramhtml5Mode`boolean`

@returns`string`

@paramsearch`string`

@returns`{ [k: string]: unknown; }`

@paramhash`string`

@returns`string`

@paramhref`string`

@returns`string`

@parampath`string`

@paramsearch`{ [k: string]: unknown; }`

@paramhash`string`

@parambaseUrl`string | undefined`

@returns`string`

@paramvalA`string`

@paramvalB`string`

@returns`boolean`

@paramurl`string`

@parambase`string | undefined`

@returns`{ href: string; protocol: string; host: string; search: string; hash: string; hostname: string; port: string; pathname: string; }`

Jump to details

----
url: https://angular.dev/cli/analytics/enable
----

```
ng analytics enable [options]
```

```
ng analytics on [options]
```

Enables analytics gathering and reporting for the user.

## [Options](/cli/analytics/enable#options)

`global`

Alias`g`

Configure analytics gathering and reporting globally in the caller's home directory.

Value Type`boolean`Default`false`

Jump to details

----
url: https://angular.dev/tutorials/first-app/01-hello-world
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/02-Home)

[](/tutorials/first-app/02-Home)

This first lesson serves as the starting point from which each lesson in this tutorial adds new features to build a complete Angular app. In this lesson, we'll update the application to display the famous text, "Hello World".

[](https://www.youtube.com/watch?v=UnOwDuliqZA\&autoplay=1)

## [What you'll learn](#what-youll-learn)

If your application runs successfully in the browser and displays the executed content without errors, it confirms that your development environment and IDE are correctly set up to begin building an Angular app.

**NOTE:** If you are working with the embedded editor, skip to [step four](#create-hello-world). When working in the browser playground, you do not need to `ng serve` to run the app. Other commands like `ng generate` can be done in the console window to your right.

1. ### [Download the default app](#download-the-default-app)

   Start by clicking the "Download" icon in the top right pane of the code editor. This will download a `.zip` file containing the source code for this tutorial. Open this in your local Terminal and IDE then move on to testing the default app.

   At any step in the tutorial, you can click this icon to download the step's source code and start from there.

2. ### [Test the default app](#test-the-default-app)

   In this step, after you download the default starting app, you build the default Angular app. This confirms that your development environment has what you need to continue the tutorial.

   In the **Terminal** pane of your IDE:

   1. In your project directory, navigate to the `first-app` directory.

   2. Run this command to install the dependencies needed to run the app.

      ```
      npm install
      ```

   3. Run this command to build and serve the default app.

      ```
      ng serve
      ```

      The app should build without errors.

   4. In a web browser on your development computer, open `http://localhost:4200`.

   5. Confirm that the default web site appears in the browser.

   6. You can leave `ng serve` running as you complete the next steps.

3. ### [Review the files in the project](#review-the-files-in-the-project)

   In this step, you get to know the files that make up a default Angular app.

   In the **Explorer** pane of your IDE:

   1. In your project directory, navigate to the `first-app` directory.

   2. Open the `src` directory to see these files.

      1. In the file explorer, find the Angular app files (`/src`).

         1. `index.html` is the app's top level HTML template.
         2. `styles.css` is the app's top level style sheet.
         3. `main.ts` is where the app starts running.
         4. `favicon.ico` is the app's icon, just as you would find in any web site.

      2. In the file explorer, find the Angular app's component files (`/app`).

         1. `app.ts` is the source file that describes the `app-root` component. This is the top-level Angular component in the app. A component is the basic building block of an Angular application. The component description includes the component's code, HTML template, and styles, which can be described in this file, or in separate files.

            In this app, the styles are in a separate file while the component's code and HTML template are in this file.

         2. `app.css` is the style sheet for this component.

         3. New components are added to this directory.

      3. In the file explorer, find the image directory (`/public`) that contains images used by the app.

      4. In the file explorer, find the files and directories that an Angular app needs to build and run, but they are not files that you normally interact with.

         1. `.angular` has files required to build the Angular app.
         2. `.e2e` has files used to test the app.
         3. `.node_modules` has the node.js packages that the app uses.
         4. `angular.json` describes the Angular app to the app building tools.
         5. `package.json` is used by `npm` (the node package manager) to run the finished app.
         6. `tsconfig.*` are the files that describe the app's configuration to the TypeScript compiler.

   After you have reviewed the files that make up an Angular app project, continue to the next step.

4. ### [Create `Hello World`](#create-hello-world)

   In this step, you update the Angular project files to change the displayed content.

   In your IDE:

   1. Open `first-app/src/index.html`.

      **NOTE:** This step and the next are only for your local environment!

   2. In `index.html`, replace the `<title>` element with this code to update the title of the app.

      ### Replace in src/index.html

      ```
      <!doctype html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <title>Homes</title>
          <base href="/" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          <link rel="icon" type="image/x-icon" href="favicon.ico" />
          <link
            href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:ital,wght@0,400;0,700;1,400;1,700&display=swap"
            rel="stylesheet"
          />
        </head>
        <body>
          <app-root></app-root>
        </body>
      </html>
      ```

      Then, save the changes you just made to `index.html`.

   3. Next, open `first-app/src/app/app.ts`.

   4. In `app.ts`, in the [`@Component`](/api/core/Component) definition, replace the `template` line with this code to change the text in the app component.

      ### Replace in src/app/app.ts

      ```
      import {Component} from '@angular/core';

      @Component({
        selector: 'app-root',
        imports: [],
        template: ` <h1>Hello world!</h1> `,
        styleUrls: ['./app.css'],
      })
      export class App {
        title = 'homes';
      }
      ```

   5. In `app.ts`, in the `App` class definition, replace the `title` line with this code to change the component title.

      ### Replace in src/app/app.ts

      ```
      import {Component} from '@angular/core';

      @Component({
        selector: 'app-root',
        imports: [],
        template: ` <h1>Hello world!</h1> `,
        styleUrls: ['./app.css'],
      })
      export class App {
        title = 'homes';
      }
      ```

      Then, save the changes you made to `app.ts`.

   6. If you stopped the `ng serve` command from step 1, in the **Terminal** window of your IDE, run `ng serve` again.

   7. Open your browser and navigate to `localhost:4200` and confirm that the app builds without error and displays *Homes* in the title and *Hello world* in the body of your app:

**SUMMARY:** In this lesson, you updated a default Angular app to display *Hello world*. In the process, you learned about the `ng serve` command to serve your app locally for testing.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/tutorials/signals/6-two-way-binding-with-model-signals
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/5-component-communication-with-signals)[](/tutorials/signals/7-using-signals-with-services)

[](/tutorials/signals/5-component-communication-with-signals)[](/tutorials/signals/7-using-signals-with-services)

Now that you've learned [passing data to components with input signals](/tutorials/signals/5-component-communication-with-signals), let's explore Angular's [`model()`](/api/core/model) API for two-way binding. Model signals are perfect for UI components like checkboxes, sliders, or custom form controls where the component needs to both receive a value AND update it.

In this activity, you'll create a custom checkbox component that manages its own state while keeping the parent synchronized.

***

1. ### [Set up the custom checkbox with model signal](#set-up-the-custom-checkbox-with-model-signal)

   Create a model signal in the `custom-checkbox` component that can both receive and update the parent's value.

   ```
   // Add imports for model signals
   import {Component, model, input, ChangeDetectionStrategy} from '@angular/core';

   // Model signal for two-way binding
   checked = model.required<boolean>();

   // Optional input for label
   label = input<string>('');
   ```

   Unlike [`input()`](/api/core/input) signals which are read-only, [`model()`](/api/core/model) signals can be both read and written to.

2. ### [Create the checkbox template](#create-the-checkbox-template)

   Build the checkbox template that responds to clicks and updates its own model.

   ```
   <label class="custom-checkbox">
     <input type="checkbox" [checked]="checked()" (change)="toggle()" />
     <span class="checkmark"></span>
     {{ label() }}
   </label>
   ```

   The component reads from its model signal and has a method to update it.

3. ### [Add the toggle method](#add-the-toggle-method)

   Implement the toggle method that updates the model signal when the checkbox is clicked.

   ```
   toggle() {
     // This updates BOTH the component's state AND the parent's model!
     this.checked.set(!this.checked());
   }
   ```

   When the child component calls `this.checked.set()`, it automatically propagates the change back to the parent. This is the key difference from [`input()`](/api/core/input) signals.

4. ### [Set up two-way binding in the parent](#set-up-two-way-binding-in-the-parent)

   First, uncomment the model signal properties and methods in `app.ts`:

   ```
   // Parent signal models
   agreedToTerms = model(false);
   enableNotifications = model(true);

   // Methods to test two-way binding
   toggleTermsFromParent() {
     this.agreedToTerms.set(!this.agreedToTerms());
   }

   resetAll() {
     this.agreedToTerms.set(false);
     this.enableNotifications.set(false);
   }
   ```

   Then update the template:

   Part 1. **Uncomment the checkboxes and add two-way binding:**

   * Replace `___ADD_TWO_WAY_BINDING___` with `[(checked)]="agreedToTerms"` for the first checkbox
   * Replace `___ADD_TWO_WAY_BINDING___` with `[(checked)]="enableNotifications"` for the second

   Part 2. **Replace the `???` placeholders with @if blocks:**

   ```
   @if (agreedToTerms()) {
     Yes
   } @else {
     No
   }

   @if (enableNotifications()) {
     Yes
   } @else {
     No
   }
   ```

   Part 3. **Add click handlers to the buttons:**

   ```
   <button (click)="toggleTermsFromParent()">Toggle Terms from Parent</button>
   <button (click)="resetAll()">Reset All</button>
   ```

   The `[(checked)]` syntax creates two-way binding - data flows down to the component AND changes flow back up to the parent by emitting an event that references the signal itself and does *not* call the signal getter directly.

5. ### [Test the two-way binding](#test-the-two-way-binding)

   Interact with your app to see two-way binding in action:

   1. **Click checkboxes** - Component updates its own state and notifies parent
   2. **Click "Toggle Terms from Parent"** - Parent updates propagate down to component
   3. **Click "Reset All"** - Parent resets both models and components update automatically

   Both the parent and child can update the shared state, and both stay in sync automatically!

Perfect! You've learned how model signals enable two-way binding:

* **Model signals** - Use [`model()`](/api/core/model) and [`model.required()`](/api/core/model) for values that can be both read and written
* **Two-way binding** - Use `[(property)]` syntax to bind parent signals to child models
* **Perfect for UI components** - Checkboxes, form controls, and widgets that need to manage their own state
* **Automatic synchronization** - Parent and child stay in sync without manual event handling

**When to use [`model()`](/api/core/model) vs [`input()`](/api/core/input):**

* Use [`input()`](/api/core/input) for data that only flows down (display data, configuration)
* Use [`model()`](/api/core/model) for UI components that need to update their own value (form controls, toggles)

In the next lesson, you'll learn about [using signals with services](/tutorials/signals/7-using-signals-with-services)!

----
url: https://angular.dev/api/core/testing/DirectiveFixture
----

```
    
      class DirectiveFixture<T> extends AbstractFixture<Element> {
}
    
    
```

@paramhostRef`ComponentRef<unknown>`

@paramdirectiveInstance`T`

@returns`DirectiveFixture<T>`

The instance of the directive class.

Registers a callback that will be invoked when the fixture is destroyed.

@paramcallback`() => void`

@returns`void`

----
url: https://angular.dev/api/forms/signals/createManagedMetadataKey
----

```
function createManagedMetadataKey<TRead, TWrite>(
  create: (
    state: FieldState<unknown, string | number>,
    data: Signal<TWrite | undefined>,
  ) => TRead,
): MetadataKey<TRead, TWrite, TWrite | undefined>;
function createManagedMetadataKey<TRead, TWrite, TAcc>(
  create: (
    state: FieldState<unknown, string | number>,
    data: Signal<TAcc>,
  ) => TRead,
  reducer: MetadataReducer<TAcc, TWrite>,
): MetadataKey<TRead, TWrite, TAcc>;
```

Creates a metadata key that exposes a managed value based on the accumulated result of the values written to the key. The accumulated value takes the last value set on a given field tree node, overriding any previously set values.

@paramcreate`(state: FieldState<unknown, string | number>, data: Signal<TWrite | undefined>) => TRead`

A function that receives a signal of the accumulated value and returns the managed value based on it. This function runs during the construction of the [`FieldTree`](/api/forms/signals/FieldTree) node, and runs in the injection context of that node.

@returns`MetadataKey<TRead, TWrite, TWrite | undefined>`

Creates a metadata key that exposes a managed value based on the accumulated result of the values written to the key.

@paramcreate`(state: FieldState<unknown, string | number>, data: Signal<TAcc>) => TRead`

A function that receives a signal of the accumulated value and returns the managed value based on it. This function runs during the construction of the [`FieldTree`](/api/forms/signals/FieldTree) node, and runs in the injection context of that node.

@paramreducer`MetadataReducer<TAcc, TWrite>`

The reducer used to combine individual value written to the key, this will determine the accumulated value that the create function receives.

@returns`MetadataKey<TRead, TWrite, TAcc>`

Jump to details

----
url: https://angular.dev/api/common/CommonModule
----

## [API](/api/common/CommonModule#api)

```
    
      class CommonModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/provideCloudinaryLoader
----

```
    
      const provideCloudinaryLoader: (path: string) => Provider[];
    
    
```

Jump to details

----
url: https://angular.dev/api/core/forwardRef
----

```
function forwardRef(forwardRefFn: ForwardRefFn): Type<any>;
```

@paramforwardRefFn`ForwardRefFn`

@returns`Type<any>`

## [Description](#description)

Allows to refer to references which are not yet defined.

For instance, [`forwardRef`](/api/core/forwardRef) is used when the `token` which we need to refer to for the purposes of DI is declared, but not yet defined. It is also used when the `token` which we use when creating a query is not yet defined.

[`forwardRef`](/api/core/forwardRef) is also used to break circularities in standalone components imports.

## [Usage Notes](#usage-notes)

### [Circular dependency example](#circular-dependency-example)

```
@Injectable()
      class Door {
        lock: Lock;

        // Door attempts to inject Lock, despite it not being defined yet.
        // forwardRef makes this possible.
        constructor(@Inject(forwardRef(() => Lock)) lock: Lock) {
          this.lock = lock;
        }
      }

      // Only at this point Lock is defined.
      class Lock {}

      const injector = Injector.create({
        providers: [
          {provide: Lock, deps: []},
          {provide: Door, deps: [Lock]},
        ],
      });

      expect(injector.get(Door) instanceof Door).toBe(true);
      expect(injector.get(Door).lock instanceof Lock).toBe(true);
```

### [Circular standalone reference import example](#circular-standalone-reference-import-example)

```
@Component({
  imports: [ChildComponent],
  selector: 'app-parent',
  template: `<app-child [hideParent]="hideParent()"/>`,
})
export class ParentComponent {
   hideParent = input.required<boolean>();
}


@Component({
  imports: [forwardRef(() => ParentComponent)],
  selector: 'app-child',
  template: `
   @if(!hideParent()) {
      <app-parent/>
   }
 `,
})
export class ChildComponent {
   hideParent = input.required<boolean>();
}
```

Jump to details

----
url: https://angular.dev/api/forms/NgControlStatus
----

```
    
      class NgControlStatus extends AbstractControlStatus {}
    
    
```

@paramcd`NgControl`

@returns`NgControlStatus`

## [Description](#description)

Directive automatically applied to Angular form controls that sets CSS classes based on control status.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [CSS classes applied](#css-classes-applied)

The following classes are applied as the properties become true:

* ng-valid
* ng-invalid
* ng-pending
* ng-pristine
* ng-dirty
* ng-untouched
* ng-touched

Jump to details

----
url: https://angular.dev/api/router/withPreloading
----

```
function withPreloading(
  preloadingStrategy: Type<PreloadingStrategy>,
): PreloadingFeature;
```

@parampreloadingStrategy`Type<PreloadingStrategy>`

A reference to a class that implements a [`PreloadingStrategy`](/api/router/PreloadingStrategy) that should be used.

@returns`PreloadingFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can configure preloading:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withPreloading(PreloadAllModules))
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/api/forms/NonNullableFormBuilder
----

```
    
      abstract class NonNullableFormBuilder {}
    
    
```

Similar to [`FormBuilder#group`](/api/forms/FormBuilder#group), except any implicitly constructed [`FormControl`](/api/forms/FormControl) will be non-nullable (i.e. it will have `nonNullable` set to true). Note that already-constructed controls will not be altered.

@paramcontrols`T`

@paramoptions`AbstractControlOptions | null | undefined`

@returns`FormGroup<ɵNonNullableFormControls<T>>`

Similar to [`FormBuilder#record`](/api/forms/FormBuilder#record), except any implicitly constructed [`FormControl`](/api/forms/FormControl) will be non-nullable (i.e. it will have `nonNullable` set to true). Note that already-constructed controls will not be altered.

@paramcontrols`{ [key: string]: T; }`

@paramoptions`AbstractControlOptions | null | undefined`

@returns`FormRecord<ɵElement<T, never>>`

Similar to [`FormBuilder#array`](/api/forms/FormBuilder#array), except any implicitly constructed [`FormControl`](/api/forms/FormControl) will be non-nullable (i.e. it will have `nonNullable` set to true). Note that already-constructed controls will not be altered.

@paramcontrols`T[]`

@paramvalidatorOrOpts`ValidatorFn | AbstractControlOptions | ValidatorFn[] | null | undefined`

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

@returns`FormArray<ɵElement<T, never>>`

Similar to [`FormBuilder#control`](/api/forms/FormBuilder#control), except this overridden version of `control` forces `nonNullable` to be `true`, resulting in the control always being non-nullable.

@paramformState`T | FormControlState<T>`

@paramvalidatorOrOpts`ValidatorFn | AbstractControlOptions | ValidatorFn[] | null | undefined`

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

@returns`FormControl<T>`

Jump to details

----
url: https://angular.dev/api/router/ViewTransitionsFeature
----

```
    
      type ViewTransitionsFeature = RouterFeature<RouterFeatureKind.ViewTransitionsFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragStart
----

```
    
      interface CdkDragStart<T = any> {}
    
    
```

Draggable that emitted the event.

Native event that started the drag sequence.

Jump to details

----
url: https://angular.dev/api/aria/listbox/Listbox
----

```
    
      class Listbox<V> implements OnDestroy {}
    
    
```

A unique identifier for the listbox.

A reference to the host element.

A signal wrapper for directionality.

Whether the list is vertically or horizontally oriented.

Whether multiple items in the list can be selected at once.

Whether focus should wrap when navigating.

Whether to allow disabled items to receive focus. When `true`, disabled items are focusable but not interactive. When `false`, disabled items are skipped during navigation.

The focus strategy used by the list.

* `roving`: Focus is moved to the active item using `tabindex`.
* `activedescendant`: Focus remains on the listbox container, and `aria-activedescendant` is used to indicate the active item.

The selection strategy used by the list.

* `follow`: The focused item is automatically selected.
* `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar).

The amount of time before the typeahead search is reset.

Whether the listbox is disabled.

Whether the listbox is readonly.

The tabindex of the listbox.

The values of the currently selected items.

The ID of the active descendant in the listbox.

@returns`void`

@paramoptions`ScrollIntoViewOptions`

@returns`void`

Navigates to the first item in the listbox.

@returns`void`

Navigates to an item at a specific index in the listbox.

@paramindex`number`

@returns`void`

## [Description](#description)

Represents a container used to display a list of items for a user to select from.

The `ngListbox` is meant to be used in conjunction with `ngOption` directives to create a selectable list. It supports single and multiple selection modes, as well as various focus and orientation strategies.

```
<ul ngListbox [(value)]="selectedItems" [multi]="true" orientation="vertical">
  @for (item of items; track item.id) {
    <li ngOption [value]="item.id" [label]="item.name" [disabled]="item.disabled">
      {{item.name}}
    </li>
  }
</ul>
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpUploadProgressEvent
----

## [API](/api/common/http/HttpUploadProgressEvent#api)

```
    
      interface HttpUploadProgressEvent extends HttpProgressEvent {}
    
    
```

Number of bytes uploaded or downloaded.

Total number of bytes to upload or download. Depending on the request or response, this may not be computable and thus may not be present.

## [Description](/api/common/http/HttpUploadProgressEvent#description)

An upload progress event.

Note: The [`FetchBackend`](/api/common/http/FetchBackend) doesn't support progress report on uploads.

Jump to details

----
url: https://angular.dev/api/localize/clearTranslations
----

```
function clearTranslations(): void;
```

@returns`void`

## [Description](#description)

Remove all translations for `$localize`, if doing runtime translation.

All translations that had been loading into memory using [`loadTranslations()`](/api/localize/loadTranslations) will be removed.

Jump to details

----
url: https://angular.dev/extended-diagnostics
----

There are many coding patterns that are technically valid to the compiler or runtime, but which may have complex nuances or caveats. These patterns may not have the intended effect expected by a developer, which often leads to bugs. The Angular compiler includes "extended diagnostics" which identify many of these patterns, in order to warn developers about the potential issues and enforce common best practices within a codebase.

## [Diagnostics](/extended-diagnostics#diagnostics)

Currently, Angular supports the following extended diagnostics:

| Code     | Name                                                                  |
| :------- | :-------------------------------------------------------------------- |
| `NG8101` | [`invalidBananaInBox`](extended-diagnostics/NG8101)                   |
| `NG8102` | [`nullishCoalescingNotNullable`](extended-diagnostics/NG8102)         |
| `NG8103` | [`missingControlFlowDirective`](extended-diagnostics/NG8103)          |
| `NG8104` | [`textAttributeNotBinding`](extended-diagnostics/NG8104)              |
| `NG8105` | [`missingNgForOfLet`](extended-diagnostics/NG8105)                    |
| `NG8106` | [`suffixNotSupported`](extended-diagnostics/NG8106)                   |
| `NG8107` | [`optionalChainNotNullable`](extended-diagnostics/NG8107)             |
| `NG8108` | [`skipHydrationNotStatic`](extended-diagnostics/NG8108)               |
| `NG8109` | [`interpolatedSignalNotInvoked`](extended-diagnostics/NG8109)         |
| `NG8111` | [`uninvokedFunctionInEventBinding`](extended-diagnostics/NG8111)      |
| `NG8112` | [`unusedLetDeclaration`](extended-diagnostics/NG8112)                 |
| `NG8113` | [`unusedStandaloneImports`](extended-diagnostics/NG8113)              |
| `NG8114` | [`unparenthesizedNullishCoalescing`](extended-diagnostics/NG8114)     |
| `NG8115` | [`uninvokedTrackFunction`](extended-diagnostics/NG8115)               |
| `NG8116` | [`missingStructuralDirective`](extended-diagnostics/NG8116)           |
| `NG8117` | [`uninvokedFunctionInTextInterpolation`](extended-diagnostics/NG8117) |
| `NG8021` | [`deferTriggerMisconfiguration`](extended-diagnostics/NG8021)         |

## [Configuration](/extended-diagnostics#configuration)

Extended diagnostics are warnings by default and do not block compilation. Each diagnostic can be configured as either:

| Error category | Effect                                                                                                                                                                   |
| :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `warning`      | Default - The compiler emits the diagnostic as a warning but does not block compilation. The compiler will still exist with status code 0, even if warnings are emitted. |
| `error`        | The compiler emits the diagnostic as an error and fails the compilation. The compiler will exit with a non-zero status code if one or more errors are emitted.           |
| `suppress`     | The compiler does *not* emit the diagnostic at all.                                                                                                                      |

Check severity can be configured as an [Angular compiler option](reference/configs/angular-compiler-options):

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "invalidBananaInBox": "suppress"
      },
      "defaultCategory": "error"
    }
  }
}
```

The `checks` field maps the name of individual diagnostics to their associated category. See [Diagnostics](/extended-diagnostics#diagnostics) for a complete list of extended diagnostics and the name to use for configuring them.

The `defaultCategory` field is used for any diagnostics that are not explicitly listed under `checks`. If not set, such diagnostics will be treated as `warning`.

Extended diagnostics will emit when [`strictTemplates`](tools/cli/template-typecheck#strict-mode) is enabled. This is required to allow the compiler to better understand Angular template types and provide accurate and meaningful diagnostics.

## [Semantic Versioning](/extended-diagnostics#semantic-versioning)

The Angular team intends to add or enable new extended diagnostics in **minor** versions of Angular (see [semver](https://docs.npmjs.com/about-semantic-versioning)). This means that upgrading Angular may show new warnings in your existing codebase. This enables the team to deliver features more quickly and to make extended diagnostics more accessible to developers.

However, setting `"defaultCategory": "error"` will promote such warnings to hard errors. This can cause a minor version upgrade to introduce compilation errors, which may be seen as a semver non-compliant breaking change. Any new diagnostics can be suppressed or demoted to warnings via the above [configuration](/extended-diagnostics#configuration), so the impact of a new diagnostic should be minimal to projects that treat extended diagnostics as errors by default. Defaulting to error is a very powerful tool; just be aware of this semver caveat when deciding if `error` is the right default for your project.

## [New Diagnostics](/extended-diagnostics#new-diagnostics)

The Angular team is always open to suggestions about new diagnostics that could be added. Extended diagnostics should generally:

* Detect a common, non-obvious developer mistake with Angular templates
* Clearly articulate why this pattern can lead to bugs or unintended behavior
* Suggest one or more clear solutions
* Have a low, preferably zero, false-positive rate
* Apply to the vast majority of Angular applications (not specific to an unofficial library)
* Improve program correctness or performance (not style, that responsibility falls to a linter)

If you have an idea for an extended diagnostic which fits these criteria, consider filing a [feature request](https://github.com/angular/angular/issues/new?template=2-feature-request.yaml).

----
url: https://angular.dev/api/animations/animate
----

```
function animate(
  timings: string | number,
  styles?: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata | null,
): AnimationAnimateMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramtimings`string | number`

Sets [`AnimateTimings`](/api/animations/AnimateTimings) for the parent animation. A string in the format "duration \[delay] \[easing]".

* Duration and delay are expressed as a number and optional time unit, such as "1s" or "10ms" for one second and 10 milliseconds, respectively. The default unit is milliseconds.
* The easing value controls how the animation accelerates and decelerates during its runtime. Value is one of `ease`, `ease-in`, `ease-out`, `ease-in-out`, or a `cubic-bezier()` function call. If not supplied, no easing is applied.

For example, the string "1s 100ms ease-out" specifies a duration of 1000 milliseconds, and delay of 100 ms, and the "ease-out" easing style, which decelerates near the end of the duration.

@paramstyles`AnimationStyleMetadata | AnimationKeyframesSequenceMetadata | null`

Sets AnimationStyles for the parent animation. A function call to either [`style()`](/api/animations/style) or [`keyframes()`](/api/animations/keyframes) that returns a collection of CSS style entries to be applied to the parent animation. When null, uses the styles from the destination state. This is useful when describing an animation step that will complete an animation; see "Animating to the final state" in `transitions()`.

@returns`AnimationAnimateMetadata`

An object that encapsulates the animation step.

## [Usage Notes](#usage-notes)

Call within an animation [`sequence()`](/api/animations/sequence), [`group()`](/api/animations/group), or [`transition()`](/api/animations/transition) call to specify an animation step that applies given style data to the parent animation for a given amount of time.

### [Syntax Examples](#syntax-examples)

**Timing examples**

The following examples show various `timings` specifications.

* `animate(500)` : Duration is 500 milliseconds.
* `animate("1s")` : Duration is 1000 milliseconds.
* `animate("100ms 0.5s")` : Duration is 100 milliseconds, delay is 500 milliseconds.
* `animate("5s ease-in")` : Duration is 5000 milliseconds, easing in.
* `animate("5s 10ms cubic-bezier(.17,.67,.88,.1)")` : Duration is 5000 milliseconds, delay is 10 milliseconds, easing according to a bezier curve.

**Style examples**

The following example calls [`style()`](/api/animations/style) to set a single CSS style.

```
animate(500, style({ background: "red" }))
```

The following example calls [`keyframes()`](/api/animations/keyframes) to set a CSS style to different values for successive keyframes.

```
animate(500, keyframes(
 [
  style({ background: "blue" }),
  style({ background: "red" })
 ])
```

Jump to details

----
url: https://angular.dev/api/aria/accordion/ACCORDION_GROUP
----

```
    
      const ACCORDION_GROUP: InjectionToken<AccordionGroup>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/StreamingResourceOptions
----

```
    
      interface StreamingResourceOptions<T, R> extends BaseResourceOptions<T, R> {}
    
    
```

Loading function which returns a `Promise` of a signal of the resource's value for a given request, which can change over time as new values are received from a stream.

----
url: https://angular.dev/api/platform-browser/SafeResourceUrl
----

```
    
      interface SafeResourceUrl extends SafeValue {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/ssr
----

Angular ships all applications as client-side rendered (CSR) by default. While this approach delivers an initial payload that's lightweight, it introduces trade-offs including slower load times, degraded performance metrics, and higher resource demands since the user's device performs most of the computations. As a result, many applications achieve significant performance improvements by integrating server-side rendering (SSR) into a hybrid rendering strategy.

## [What is hybrid rendering?](#what-is-hybrid-rendering)

Hybrid rendering allows developers to leverage the benefits of server-side rendering (SSR), pre-rendering (also known as "static site generation" or SSG) and client-side rendering (CSR) to optimize your Angular application. It gives you fine-grained control over how the different parts of your app are rendered to give your users the best experience possible.

## [Setting up hybrid rendering](#setting-up-hybrid-rendering)

You can create a **new** project with hybrid rendering by using the server-side rendering flag (i.e., `--ssr`) with the Angular CLI `ng new` command:

```
ng new --ssr
```

You can also enable hybrid rendering by adding server-side rendering to an existing project with the `ng add` command:

```
ng add @angular/ssr
```

**NOTE:** By default, Angular prerenders your entire application and generates a server file. To disable this and create a fully static app, set `outputMode` to `static`. To enable SSR, update the server routes to use [`RenderMode.Server`](/api/ssr/RenderMode#Server). For more details, see [`Server routing`](#server-routing) and [`Generate a fully static application`](#generate-a-fully-static-application).

## [Server routing](#server-routing)

### [Configuring server routes](#configuring-server-routes)

You can create a server route config by declaring an array of [`ServerRoute`](api/ssr/ServerRoute "API reference") objects. This configuration typically lives in a file named `app.routes.server.ts`.

### app.routes.server.ts

```
import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: '', // This renders the "/" route on the client (CSR)
    renderMode: RenderMode.Client,
  },
  {
    path: 'about', // This page is static, so we prerender it (SSG)
    renderMode: RenderMode.Prerender,
  },
  {
    path: 'profile', // This page requires user-specific data, so we use SSR
    renderMode: RenderMode.Server,
  },
  {
    path: '**', // All other routes will be rendered on the server (SSR)
    renderMode: RenderMode.Server,
  },
];
```

You can add this config to your application with [`provideServerRendering`](api/ssr/provideServerRendering "API reference") using the [`withRoutes`](api/ssr/withRoutes "API reference") function:

### app.config.server.ts

```
import {provideServerRendering, withRoutes} from '@angular/ssr';
import {serverRoutes} from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(withRoutes(serverRoutes)),
    // ... other providers ...
  ],
};
```

When using the [App shell pattern](ecosystem/service-workers/app-shell), you must specify the component to be used as the app shell for client-side rendered routes. To do this, use the [`withAppShell`](api/ssr/withAppShell "API reference") feature:

```
import {provideServerRendering, withRoutes, withAppShell} from '@angular/ssr';
import {AppShell} from './app-shell';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(withRoutes(serverRoutes), withAppShell(AppShell)),
    // ... other providers ...
  ],
};
```

### [Rendering modes](#rendering-modes)

The server routing configuration lets you specify how each route in your application should render by setting a [`RenderMode`](api/ssr/RenderMode "API reference"):

| Rendering mode      | Description                                                                                                 |
| ------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Server (SSR)**    | Renders the application on the server for each request, sending a fully populated HTML page to the browser. |
| **Client (CSR)**    | Renders the application in the browser. This is the default Angular behavior.                               |
| **Prerender (SSG)** | Prerenders the application at build time, generating static HTML files for each route.                      |

```
import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'profile',
    renderMode: RenderMode.Server,
    headers: {
      'X-My-Custom-Header': 'some-value',
    },
    status: 201,
  },
  // ... other routes
];
```

```
import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'post/:id',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      const dataService = inject(PostService);
      const ids = await dataService.getIds(); // Assuming this returns ['1', '2', '3']

      return ids.map((id) => ({id})); // Generates paths like: /post/1, /post/2, /post/3
    },
  },
  {
    path: 'post/:id/**',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      return [
        {id: '1', '**': 'foo/3'},
        {id: '2', '**': 'bar/4'},
      ]; // Generates paths like: /post/1/foo/3, /post/2/bar/4
    },
  },
];
```

```
import {RenderMode, PrerenderFallback, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'post/:id',
    renderMode: RenderMode.Prerender,
    fallback: PrerenderFallback.Client, // Fallback to CSR if not prerendered
    async getPrerenderParams() {
      // This function returns an array of objects representing prerendered posts at the paths:
      // `/post/1`, `/post/2`, and `/post/3`.
      // The path `/post/4` will utilize the fallback behavior if it's requested.
      return [{id: '1'}, {id: '2'}, {id: '3'}];
    },
  },
];
```

## [Authoring server-compatible components](#authoring-server-compatible-components)

Some common browser APIs and capabilities might not be available on the server. Applications cannot make use of browser-specific global objects like `window`, `document`, `navigator`, or `location` as well as certain properties of `HTMLElement`.

In general, code which relies on browser-specific symbols should only be executed in the browser, not on the server. This can be enforced through the [`afterEveryRender`](/api/core/afterEveryRender) and [`afterNextRender`](/api/core/afterNextRender) lifecycle hooks. These are only executed on the browser and skipped on the server.

```
import {Component, ElementRef, viewChild, afterNextRender} from '@angular/core';

@Component({
  selector: 'my-cmp',
  template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
  contentRef = viewChild.required<ElementRef>('content');

  constructor() {
    afterNextRender(() => {
      // Safe to check `scrollHeight` because this will only run in the browser, not the server.
      console.log('content height: ' + this.contentRef().nativeElement.scrollHeight);
    });
  }
}
```

```
export abstract class AnalyticsService {
  abstract trackEvent(name: string): void;
}
```

Create the browser implementation:

```
@Injectable()
export class BrowserAnalyticsService implements AnalyticsService {
  trackEvent(name: string): void {
    // Sends the event to the browser-based third-party analytics provider
  }
}
```

Create the server implementation:

```
@Injectable()
export class ServerAnalyticsService implements AnalyticsService {
  trackEvent(name: string): void {
    // Records the event on the server
  }
}
```

Register the browser implementation in your main application configuration:

### app.config.ts

```
export const appConfig: ApplicationConfig = {
  providers: [{provide: AnalyticsService, useClass: BrowserAnalyticsService}],
};
```

Override with the server implementation in your server configuration:

### app.config.server.ts

```
const serverConfig: ApplicationConfig = {
  providers: [{provide: AnalyticsService, useClass: ServerAnalyticsService}],
};
```

Inject and use the service in your components:

```
@Component(/* ... */)
export class Checkout {
  private analytics = inject(AnalyticsService);

  onAction() {
    this.analytics.trackEvent('action');
  }
}
```

## [Accessing Document via DI](#accessing-document-via-di)

When working with server-side rendering, you should avoid directly referencing browser-specific globals like `document`. Instead, use the [`DOCUMENT`](api/core/DOCUMENT) token to access the document object in a platform-agnostic way.

```
import {inject, DOCUMENT, Service} from '@angular/core';

@Service()
export class CanonicalLinkService {
  private readonly document = inject(DOCUMENT);

  // During server rendering, inject a <link rel="canonical"> tag
  // so the generated HTML includes the correct canonical URL
  setCanonical(href: string): void {
    const link = this.document.createElement('link');
    link.rel = 'canonical';
    link.href = href;
    this.document.head.appendChild(link);
  }
}
```

**HELPFUL:** For managing meta tags, Angular provides the [`Meta`](/api/platform-browser/Meta) service.

## [Accessing Request and Response via DI](#accessing-request-and-response-via-di)

The `@angular/core` package provides several tokens for interacting with the server-side rendering environment. These tokens give you access to crucial information and objects within your Angular application during SSR.

* **[`REQUEST`](api/core/REQUEST "API reference"):** Provides access to the current request object, which is of type [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) from the Web API. This allows you to access headers, cookies, and other request information.
* **[`RESPONSE_INIT`](api/core/RESPONSE_INIT "API reference"):** Provides access to the response initialization options, which is of type [`ResponseInit`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#parameters) from the Web API. This allows you to set headers and the status code for the response dynamically. Use this token to set headers or status codes that need to be determined at runtime.
* **[`REQUEST_CONTEXT`](api/core/REQUEST_CONTEXT "API reference"):** Provides access to additional context related to the current request. This context can be passed as the second parameter of the [`handle`](api/ssr/AngularAppEngine#handle "API reference") function. Typically, this is used to provide additional request-related information that is not part of the standard Web API.

```
import {inject, REQUEST} from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `<h1>My Component</h1>`,
})
export class MyComponent {
  constructor() {
    const request = inject(REQUEST);
    console.log(request?.url);
  }
}
```

```
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "outputMode": "static"
          }
        }
      }
    }
  }
}
```

## [Caching data when using HttpClient](#caching-data-when-using-httpclient)

[`HttpClient`](/api/common/http/HttpClient) caches outgoing network requests when running on the server. This information is serialized and transferred to the browser as part of the initial HTML sent from the server. In the browser, [`HttpClient`](/api/common/http/HttpClient) checks whether it has data in the cache and if so, reuses it instead of making a new HTTP request during initial application rendering. [`HttpClient`](/api/common/http/HttpClient) stops using the cache once an application becomes [stable](api/core/ApplicationRef#isStable) while running in a browser.

### [Configuring the response body size limit](#configuring-the-response-body-size-limit)

When [`HttpClient`](/api/common/http/HttpClient) uses the default fetch backend during server-side rendering, Angular limits each response body to 1 MB. This limit prevents the server from buffering unexpectedly large responses during rendering. If a response exceeds the configured limit, the request fails with the [NG02825](errors/NG02825) error.

If your application needs to fetch larger responses during server rendering, set `maxResponseBodySize` in the `provideServerRendering` options:

```
import {provideServerRendering, withRoutes} from '@angular/ssr';
import {serverRoutes} from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(
      {
        maxResponseBodySize: 5 * 1024 * 1024, // 5MB
      },
      withRoutes(serverRoutes),
    ),
  ],
};
```

```
import {
  bootstrapApplication,
  provideClientHydration,
  withHttpTransferCacheOptions,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [
    provideClientHydration(
      withHttpTransferCacheOptions({
        includeHeaders: ['ETag', 'Cache-Control'],
        filter: (req) => !req.url.includes('/api/profile'),
        includePostRequests: true,
        includeRequestsWithAuthHeaders: false,
      }),
    ),
  ],
});
```

### [`includeHeaders`](#includeheaders)

Specifies which headers from the server response should be included in cached entries.\
No headers are included by default.

```
withHttpTransferCacheOptions({
  includeHeaders: ['ETag', 'Cache-Control'],
});
```

**IMPORTANT:** Avoid including sensitive headers like authentication tokens. These can leak user‑specific data between requests.

Including `Cache-Control` in `includeHeaders` only makes that header available on the hydrated response. Angular already evaluates `Cache-Control` headers automatically when deciding whether a request or response is eligible for transfer cache.

### [`includePostRequests`](#includepostrequests)

By default, only `GET` and `HEAD` requests are cached.\
You can enable caching for `POST` requests when they are used as read operations such as GraphQL queries.

```
withHttpTransferCacheOptions({
  includePostRequests: true,
});
```

Use this only when `POST` requests are **idempotent** and safe to reuse between server and client renders.

### [`includeRequestsWithAuthHeaders`](#includerequestswithauthheaders)

Determines whether requests containing `Authorization`, `Proxy‑Authorization`, or `Cookie` headers are eligible for caching.\
By default, these are excluded to prevent caching user‑specific responses.

```
withHttpTransferCacheOptions({
  includeRequestsWithAuthHeaders: true,
});
```

Enable only when authentication headers do **not** affect the response content (for example, public tokens for analytics APIs).

### [`includeRequestsWithCredentials`](#includerequestswithcredentials)

Determines whether requests sent using `withCredentials` or Fetch API `credentials` modes (`include` or `same-origin`) are eligible for caching.\
By default, these are excluded to prevent caching user‑specific responses.

```
withHttpTransferCacheOptions({
  includeRequestsWithCredentials: true,
});
```

Enable only when credentialed requests return responses that are safe to cache.

### [`includeNonCacheableRequests`](#includenoncacheablerequests)

Determines whether requests and responses containing `Cache-Control` directives that forbid caching (`no-store`, `no-cache`, or `private`), responses with a `Set-Cookie` header, or requests using Fetch API `cache` options (`no-store` or `no-cache`), are eligible for caching.\
By default, these are excluded to respect HTTP caching controls.

```
withHttpTransferCacheOptions({
  includeNonCacheableRequests: true,
});
```

Enable only when you need to bypass cache-control restrictions for transfer caching.

### [Per‑request overrides](#perrequest-overrides)

You can override caching behavior for a specific request using the `transferCache` request option.

```
// Include specific headers for this request
http.get('/api/profile', {transferCache: {includeHeaders: ['CustomHeader']}});
```

### [Disabling caching](#disabling-caching)

You can disable HTTP caching of requests sent from the server either globally or individually.

#### [Globally](#globally)

To disable caching for all requests in your application, use the [`withNoHttpTransferCache`](/api/platform-browser/withNoHttpTransferCache) feature:

```
import {
  bootstrapApplication,
  provideClientHydration,
  withNoHttpTransferCache,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration(withNoHttpTransferCache())],
});
```

#### [Filtering](#filtering)

You can also selectively disable caching for certain requests using the [`filter`](api/common/http/HttpTransferCacheOptions) option in [`withHttpTransferCacheOptions`](/api/platform-browser/withHttpTransferCacheOptions). For example, you can disable caching for a specific API endpoint:

```
import {
  bootstrapApplication,
  provideClientHydration,
  withHttpTransferCacheOptions,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [
    provideClientHydration(
      withHttpTransferCacheOptions({
        filter: (req) => !req.url.includes('/api/sensitive-data'),
      }),
    ),
  ],
});
```

Use this option to exclude endpoints with user‑specific or dynamic data (for example `/api/profile`).

#### [Per-request](#per-request)

To disable caching for an individual request, you can specify the [`transferCache`](api/common/http/HttpRequest#transferCache) option in an [`HttpRequest`](/api/common/http/HttpRequest).

```
httpClient.get('/api/sensitive-data', {transferCache: false});
```

`HttpTransferCache` does not cache requests or responses that explicitly opt out of caching. Angular skips transfer cache entries when a request includes a `Cache-Control` header with `no-store`, `no-cache`, or `private`, or when the request uses the Fetch API `cache` option set to `no-store` or `no-cache`. Responses with `Cache-Control: no-store`, `Cache-Control: no-cache`, or `Cache-Control: private` are also not stored in the transfer cache. Responses that include a `Set-Cookie` header are likewise not stored, as they typically carry user-specific state.

**NOTE:** If your application uses different HTTP origins to make API calls on the server and on the client, the [`HTTP_TRANSFER_CACHE_ORIGIN_MAP`](/api/common/http/HTTP_TRANSFER_CACHE_ORIGIN_MAP) token allows you to establish a mapping between those origins, so that `HttpTransferCache` feature can recognize those requests as the same ones and reuse the data cached on the server during hydration on the client.

## [Configuring a server](#configuring-a-server)

### [Node.js](#nodejs)

The `@angular/ssr/node` extends `@angular/ssr` specifically for Node.js environments. It provides APIs that make it easier to implement server-side rendering within your Node.js application. For a complete list of functions and usage examples, refer to the [`@angular/ssr/node` API reference](api/ssr/node/AngularNodeAppEngine) API reference.

### server.ts

```
import {
  AngularNodeAppEngine,
  createNodeRequestHandler,
  writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';

const app = express();
const angularApp = new AngularNodeAppEngine();

app.use((req, res, next) => {
  angularApp
    .handle(req)
    .then((response) => {
      if (response) {
        writeResponseToNodeResponse(response, res);
      } else {
        next(); // Pass control to the next middleware
      }
    })
    .catch(next);
});

/**
 * The request handler used by the Angular CLI (dev-server and during build).
 */
export const reqHandler = createNodeRequestHandler(app);
```

### [Non-Node.js](#non-nodejs)

The `@angular/ssr` provides essential APIs for server-side rendering your Angular application on platforms other than Node.js. It leverages the standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects from the Web API, enabling you to integrate Angular SSR into various server environments. For detailed information and examples, refer to the [`@angular/ssr` API reference](api/ssr/AngularAppEngine).

### server.ts

```
import {AngularAppEngine, createRequestHandler} from '@angular/ssr';

const angularApp = new AngularAppEngine();

/**
 * This is a request handler used by the Angular CLI (dev-server and during build).
 */
export const reqHandler = createRequestHandler(async (req: Request) => {
  const res: Response | null = await angularApp.handle(req);

  // ...
});
```

## [Security](#security)

For detailed information on preventing Server-Side Request Forgery (SSRF) and configuring allowed hosts, see the [Server-side security](best-practices/security#preventing-server-side-request-forgery-ssrf) guide.

----
url: https://angular.dev/api/forms/signals/Field
----

```
    
      type Field<TValue, TKey extends string | number = string | number> = () => FieldState<  TValue,  TKey>
    
    
```

Jump to details

----
url: https://angular.dev/api/localize/init/$localize
----

* * [$localize](/api/localize/init/$localize)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](#api)

```
    
      const $localize: LocalizeFn;
    
    
```

## [Description](#description)

Tag a template literal string for localization.

For example:

```
$localize `some string to localize`
```

**Providing meaning, description and id**

You can optionally specify one or more of `meaning`, `description` and `id` for a localized string by pre-pending it with a colon delimited block of the form:

```
$localize`:meaning|description@@id:source message text`;

$localize`:meaning|:source message text`;
$localize`:description:source message text`;
$localize`:@@id:source message text`;
```

This format is the same as that used for `i18n` markers in Angular templates. See the [Angular i18n guide](guide/i18n/prepare#mark-text-in-component-template).

**Naming placeholders**

If the template literal string contains expressions, then the expressions will be automatically associated with placeholder names for you.

For example:

```
$localize `Hi ${name}! There are ${items.length} items.`;
```

will generate a message-source of `Hi {$PH}! There are {$PH_1} items`.

The recommended practice is to name the placeholder associated with each expression though.

Do this by providing the placeholder name wrapped in `:` characters directly after the expression. These placeholder names are stripped out of the rendered localized string.

For example, to name the `items.length` expression placeholder `itemCount` you write:

```
$localize `There are ${items.length}:itemCount: items`;
```

**Escaping colon markers**

If you need to use a `:` character directly at the start of a tagged string that has no metadata block, or directly after a substitution expression that has no name you must escape the `:` by preceding it with a backslash:

For example:

```
// message has a metadata block so no need to escape colon
$localize `:some description::this message starts with a colon (:)`;
// no metadata block so the colon must be escaped
$localize `\:this message starts with a colon (:)`;
```

```
// named substitution so no need to escape colon
$localize `${label}:label:: ${}`
// anonymous substitution so colon must be escaped
$localize `${label}\: ${}`
```

**Processing localized strings:**

There are three scenarios:

* **compile-time inlining**: the [`$localize`](/api/localize/init/$localize) tag is transformed at compile time by a transpiler, removing the tag and replacing the template literal string with a translated literal string from a collection of translations provided to the transpilation tool.

* **run-time evaluation**: the [`$localize`](/api/localize/init/$localize) tag is a run-time function that replaces and reorders the parts (static strings and expressions) of the template literal string with strings from a collection of translations loaded at run-time.

* **pass-through evaluation**: the [`$localize`](/api/localize/init/$localize) tag is a run-time function that simply evaluates the original template literal string without applying any translations to the parts. This version is used during development or where there is no need to translate the localized template literals.

Jump to details

----
url: https://angular.dev/api/router/RedirectCommand
----

```
    
      class RedirectCommand extends Error {}
    
    
```

@paramredirectTo`UrlTree`

@paramnavigationBehaviorOptions`NavigationBehaviorOptions | undefined`

@returns`RedirectCommand`

Stack trace where extra frames have been removed and zone names added.

Original stack trace with no modifications

## [Description](#description)

Can be returned by a [`Router`](/api/router/Router) guard to instruct the [`Router`](/api/router/Router) to redirect rather than continue processing the path of the in-flight navigation. The `redirectTo` indicates *where* the new navigation should go to and the optional `navigationBehaviorOptions` can provide more information about *how* to perform the navigation.

```
const route: Route = {
  path: "user/:userId",
  component: User,
  canActivate: [
    () => {
      const router = inject(Router);
      const authService = inject(AuthenticationService);

      if (!authService.isLoggedIn()) {
        const loginPath = router.parseUrl("/login");
        return new RedirectCommand(loginPath, {
          skipLocationChange: true,
        });
      }

      return true;
    },
  ],
};
```

Jump to details

----
url: https://angular.dev/api/upgrade/static/downgradeInjectable
----

```
function downgradeInjectable(token: any, downgradedModule?: string): Function;
```

@paramtoken`any`

an `InjectionToken` that identifies a service provided from Angular.

@paramdowngradedModule`string`

the name of the downgraded module (if any) that the injectable "belongs to", as returned by a call to [`downgradeModule()`](/api/upgrade/static/downgradeModule). It is the module, whose injector will be used for instantiating the injectable.\
(This option is only necessary when using [`downgradeModule()`](/api/upgrade/static/downgradeModule) to downgrade more than one Angular module.)

@returns`Function`

a [factory function](https://docs.angularjs.org/guide/di) that can be used to register the service on an AngularJS module.

## [Description](#description)

A helper function to allow an Angular service to be accessible from AngularJS.

*Part of the [upgrade/static](api?query=upgrade%2Fstatic) library for hybrid upgrade apps that support AOT compilation*

This helper function returns a factory function that provides access to the Angular service identified by the `token` parameter.

## [Usage Notes](#usage-notes)

### [Examples](#examples)

First ensure that the service to be downgraded is provided in an [`NgModule`](/api/core/NgModule) that will be part of the upgrade application. For example, let's assume we have defined `HeroesService`

```
// This Angular service will be "downgraded" to be used in AngularJS
@Injectable()
export class HeroesService {
  heroes: Hero[] = [
    {name: 'superman', description: 'The man of steel'},
    {name: 'wonder woman', description: 'Princess of the Amazons'},
    {name: 'thor', description: 'The hammer-wielding god'},
  ];

  constructor(textFormatter: TextFormatter) {
    // Change all the hero names to title case, using the "upgraded" AngularJS service
    this.heroes.forEach((hero: Hero) => (hero.name = textFormatter.titleCase(hero.name)));
  }

  addHero() {
    this.heroes = this.heroes.concat([
      {name: 'Kamala Khan', description: 'Epic shape-shifting healer'},
    ]);
  }

  removeHero(hero: Hero) {
    this.heroes = this.heroes.filter((item: Hero) => item !== hero);
  }
}
```

and that we have included this in our upgrade app [`NgModule`](/api/core/NgModule)

```
// This NgModule represents the Angular pieces of the application
@NgModule({
  declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
  providers: [
    HeroesService,
    // Register an Angular provider whose value is the "upgraded" AngularJS service
    {provide: TextFormatter, useFactory: (i: any) => i.get('textFormatter'), deps: ['$injector']},
  ],
  // We must import `UpgradeModule` to get access to the AngularJS core services
  imports: [BrowserModule, UpgradeModule],
})
export class Ng2AppModule {
}
```

Now we can register the [`downgradeInjectable`](/api/upgrade/static/downgradeInjectable) factory function for the service on an AngularJS module.

```
// Register an AngularJS service, whose value is the "downgraded" Angular injectable.
ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService) as any);
```

Inside an AngularJS component's controller we can get hold of the downgraded service via the name we gave when downgrading.

```
// This is our top level application component
ng1AppModule.component('exampleApp', {
  // We inject the "downgraded" HeroesService into this AngularJS component
  // (We don't need the `HeroesService` type for AngularJS DI - it just helps with TypeScript
  // compilation)
  controller: [
    'heroesService',
    function (this: any, heroesService: HeroesService) {
      this.heroesService = heroesService;
    },
  ],
  // This template makes use of the downgraded `ng2-heroes` component
  // Note that because its element is compiled by AngularJS we must use kebab-case attributes
  // for inputs and outputs
  template: `<link rel="stylesheet" href="./styles.css">
          <ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)">
            <h1>Heroes</h1>
            <p class="extra">There are {{ $ctrl.heroesService.heroes.length }} heroes.</p>
          </ng2-heroes>`,
});
```

When using [`downgradeModule()`](/api/upgrade/static/downgradeModule), downgraded injectables will not be available until the Angular module that provides them is instantiated. In order to be safe, you need to ensure that the downgraded injectables are not used anywhere *outside* the part of the app where it is guaranteed that their module has been instantiated.

For example, it is *OK* to use a downgraded service in an upgraded component that is only used from a downgraded Angular component provided by the same Angular module as the injectable, but it is *not OK* to use it in an AngularJS component that may be used independently of Angular or use it in a downgraded Angular component from a different module.

Jump to details

----
url: https://angular.dev/api/common/TrailingSlashPathLocationStrategy
----

```
    
      class TrailingSlashPathLocationStrategy extends PathLocationStrategy {}
    
    
```

----
url: https://angular.dev/api/core/ChangeDetectionStrategy
----

```
    
      enum ChangeDetectionStrategy {}
    
    
```

Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated until reactivated by setting the strategy to `Default` (`CheckAlways`). Change detection can still be explicitly invoked. This strategy applies to all child directives and cannot be overridden.

**NOTE:** OnPush is enabled by default.

Use the `Eager` strategy, meaning that the component is checked eagerly when the change detection traversal reaches it, rather than only checking under certain circumstances (e.g. `markForCheck`, a signal in the template changed, etc).

@deprecated

Use `Eager` instead.

This value is equivalent to setting `Eager` and is due to be removed.

Jump to details

----
url: https://angular.dev/tutorials/first-app/09-services
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/08-ngFor)[](/tutorials/first-app/10-routing)

[](/tutorials/first-app/08-ngFor)[](/tutorials/first-app/10-routing)

This tutorial lesson demonstrates how to create an Angular service and use dependency injection to include it in your app.

[](https://www.youtube.com/watch?v=-jRxG84AzCI\&autoplay=1)

## [What you'll learn](#what-youll-learn)

Your app has a service to serve the data to your app. At the end of this lesson, the service reads data from local, static data. In a later lesson, you'll update the service to get data from a web service.

## [Conceptual preview of services](#conceptual-preview-of-services)

This tutorial introduces Angular services and dependency injection.

### [Angular services](#angular-services)

*Angular services* provide a way for you to separate Angular app data and functions that can be used by multiple components in your app. To be used by multiple components, a service must be made *injectable*. Services that are injectable and used by a component become dependencies of that component. The component depends on those services and can't function without them.

### [Dependency injection](#dependency-injection)

*Dependency injection* is the mechanism that manages the dependencies of an app's components and the services that other components can use.

1. ### [Create a new service for your app](#create-a-new-service-for-your-app)

   This step creates an injectable service for your app.

   In the **Terminal** pane of your IDE:

   1. In your project directory, navigate to the `first-app` directory.

   2. In the `first-app` directory, run this command to create the new service.

      ```
      ng generate service housing --skip-tests
      ```

   3. Run `ng serve` to build the app and serve it to `http://localhost:4200`.

   4. Confirm that the app builds without error. Correct any errors before you continue to the next step.

2. ### [Add static data to the new service](#add-static-data-to-the-new-service)

   This step adds some sample data to your new service. In a later lesson, you'll replace the static data with a web interface to get data as you might in a real app. For now, your app's new service uses the data that has, so far, been created locally in `Home`.

   In the **Edit** pane of your IDE:

   1. In `src/app/home/home.ts`, from `Home`, copy the `baseUrl` variable and the `housingLocationList` variable with its array value.

   2. In `src/app/housing.service.ts`:

      1. Inside the `HousingService` class, paste the variables that you copied from `Home` in the previous step.

      2. Inside the `HousingService` class, paste these functions after the data you just copied. These functions allow dependencies to access the service's data.

         ### Service functions in src/app/housing.service.ts

         ```
         import {Service} from '@angular/core';
         import {HousingLocationInfo} from './housinglocation';
         @Service()
         export class HousingService {
           readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

           protected housingLocationList: HousingLocationInfo[] = [
             {
               id: 0,
               name: 'Acme Fresh Start Housing',
               city: 'Chicago',
               state: 'IL',
               photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`,
               availableUnits: 4,
               wifi: true,
               laundry: true,
             },
             {
               id: 1,
               name: 'A113 Transitional Housing',
               city: 'Santa Monica',
               state: 'CA',
               photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`,
               availableUnits: 0,
               wifi: false,
               laundry: true,
             },
             {
               id: 2,
               name: 'Warm Beds Housing Support',
               city: 'Juneau',
               state: 'AK',
               photo: `${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg`,
               availableUnits: 1,
               wifi: false,
               laundry: false,
             },
             {
               id: 3,
               name: 'Homesteady Housing',
               city: 'Chicago',
               state: 'IL',
               photo: `${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg`,
               availableUnits: 1,
               wifi: true,
               laundry: false,
             },
             {
               id: 4,
               name: 'Happy Homes Group',
               city: 'Gary',
               state: 'IN',
               photo: `${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg`,
               availableUnits: 1,
               wifi: true,
               laundry: false,
             },
             {
               id: 5,
               name: 'Hopeful Apartment Group',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg`,
               availableUnits: 2,
               wifi: true,
               laundry: true,
             },
             {
               id: 6,
               name: 'Seriously Safe Towns',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg`,
               availableUnits: 5,
               wifi: true,
               laundry: true,
             },
             {
               id: 7,
               name: 'Hopeful Housing Solutions',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg`,
               availableUnits: 2,
               wifi: true,
               laundry: true,
             },
             {
               id: 8,
               name: 'Seriously Safe Towns',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg`,
               availableUnits: 10,
               wifi: false,
               laundry: false,
             },
             {
               id: 9,
               name: 'Capital Safe Towns',
               city: 'Portland',
               state: 'OR',
               photo: `${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg`,
               availableUnits: 6,
               wifi: true,
               laundry: true,
             },
           ];

           getAllHousingLocations(): HousingLocationInfo[] {
             return this.housingLocationList;
           }

           getHousingLocationById(id: number): HousingLocationInfo | undefined {
             return this.housingLocationList.find((housingLocation) => housingLocation.id === id);
           }
         }
         ```

         You will need these functions in a future lesson. For now, it is enough to understand that these functions return either a specific `HousingLocation` by id or the entire list.

      3. Add a file level import for the `HousingLocation`.

         ### Import HousingLocation type in src/app/housing.service.ts

         ```
         import {Service} from '@angular/core';
         import {HousingLocationInfo} from './housinglocation';
         @Service()
         export class HousingService {
           readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

           protected housingLocationList: HousingLocationInfo[] = [
             {
               id: 0,
               name: 'Acme Fresh Start Housing',
               city: 'Chicago',
               state: 'IL',
               photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`,
               availableUnits: 4,
               wifi: true,
               laundry: true,
             },
             {
               id: 1,
               name: 'A113 Transitional Housing',
               city: 'Santa Monica',
               state: 'CA',
               photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`,
               availableUnits: 0,
               wifi: false,
               laundry: true,
             },
             {
               id: 2,
               name: 'Warm Beds Housing Support',
               city: 'Juneau',
               state: 'AK',
               photo: `${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg`,
               availableUnits: 1,
               wifi: false,
               laundry: false,
             },
             {
               id: 3,
               name: 'Homesteady Housing',
               city: 'Chicago',
               state: 'IL',
               photo: `${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg`,
               availableUnits: 1,
               wifi: true,
               laundry: false,
             },
             {
               id: 4,
               name: 'Happy Homes Group',
               city: 'Gary',
               state: 'IN',
               photo: `${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg`,
               availableUnits: 1,
               wifi: true,
               laundry: false,
             },
             {
               id: 5,
               name: 'Hopeful Apartment Group',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg`,
               availableUnits: 2,
               wifi: true,
               laundry: true,
             },
             {
               id: 6,
               name: 'Seriously Safe Towns',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg`,
               availableUnits: 5,
               wifi: true,
               laundry: true,
             },
             {
               id: 7,
               name: 'Hopeful Housing Solutions',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg`,
               availableUnits: 2,
               wifi: true,
               laundry: true,
             },
             {
               id: 8,
               name: 'Seriously Safe Towns',
               city: 'Oakland',
               state: 'CA',
               photo: `${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg`,
               availableUnits: 10,
               wifi: false,
               laundry: false,
             },
             {
               id: 9,
               name: 'Capital Safe Towns',
               city: 'Portland',
               state: 'OR',
               photo: `${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg`,
               availableUnits: 6,
               wifi: true,
               laundry: true,
             },
           ];

           getAllHousingLocations(): HousingLocationInfo[] {
             return this.housingLocationList;
           }

           getHousingLocationById(id: number): HousingLocationInfo | undefined {
             return this.housingLocationList.find((housingLocation) => housingLocation.id === id);
           }
         }
         ```

   3. Confirm that the app builds without error. Correct any errors before you continue to the next step.

3. ### [Inject the new service into `Home`](#inject-the-new-service-into-home)

   This step injects the new service into your app's `Home` so that it can read the app's data from a service. In a later lesson, you'll replace the static data with a live data source to get data as you might in a real app.

   In the **Edit** pane of your IDE, in `src/app/home/home.ts`:

   1. At the top of `src/app/home/home.ts`, add the `inject` to the items imported from `@angular/core`. This will import the `inject` function into the `Home` class.

      ### Update to src/app/home/home.ts

      ```
      import {Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';
      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of housingLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
        }
      }
      ```

   2. Add a new file level import for the `HousingService`:

      ### Add import to src/app/home/home.ts

      ```
      import {Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';
      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of housingLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
        }
      }
      ```

   3. From `Home`, delete the `baseUrl` variable, delete the `housingLocationList` array entries and assign `housingLocationList` the value of empty array (`[]`). In a few steps you will update the code to pull the data from the `HousingService`.

   4. In `Home`, add the following code to inject the new service and initialize the data for the app. The `constructor` is the first function that runs when this component is created. The code in the `constructor` will assign the `housingLocationList` the value returned from the call to `getAllHousingLocations`.

      ### Initialize data from service in src/app/home/home.ts

      ```
      import {Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';
      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of housingLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
        }
      }
      ```

   5. Save the changes to `src/app/home/home.ts` and confirm your app builds without error. Correct any errors before you continue to the next step.

**SUMMARY:** In this lesson, you added an Angular service to your app and injected it into the `Home` class. This compartmentalizes how your app gets its data. For now, the new service gets its data from a static array of data. In a later lesson, you'll refactor the service to get its data from an API endpoint.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/api/animations/browser/NoopAnimationDriver
----

[`AnimationDriver`](/api/animations/browser/AnimationDriver) implentation for Noop animations

## [API](#api)

```
    
      class NoopAnimationDriver implements AnimationDriver {}
    
    
```

@paramprop`string`

@returns`boolean`

Whether `prop` is a valid CSS property

@paramelm1`any`

@paramelm2`any`

@returns`boolean`

Whether elm1 contains elm2.

@paramelement`unknown`

@returns`unknown`

Rhe parent of the given element or `null` if the element is the `document`

@paramelement`any`

@paramselector`string`

@parammulti`boolean`

@returns`any[]`

The result of the query selector on the element. The array will contain up to 1 item if `multi` is `false`.

@paramelement`any`

@paramprop`string`

@paramdefaultValue`string | undefined`

@returns`string`

The `defaultValue` or empty string

@paramelement`any`

@paramkeyframes`Map<string, string | number>[]`

@paramduration`number`

@paramdelay`number`

@parameasing`string`

@parampreviousPlayers`any[]`

@paramscrubberAccessRequested`boolean | undefined`

@returns`AnimationPlayer`

An `NoopAnimationPlayer`

Jump to details

----
url: https://angular.dev/api/core/DoCheck
----

```
    
      interface DoCheck {}
    
    
```

A callback method that performs change-detection, invoked after the default change-detector has checked the directive's input bindings in the parent template. See [`KeyValueDiffers`](/api/core/KeyValueDiffers) and [`IterableDiffers`](/api/core/IterableDiffers) for implementing custom change checking for collections.

@returns`void`

## [Description](#description)

A lifecycle hook that invokes a custom change-detection function for a directive, in addition to the check performed by the default change-detector on the input bindings for this directive usage in the parent template. Note that this hook is invoked even when the directive's own change detection is skipped (e.g., with the `OnPush` change detection strategy). Developers might use this hook to implement a custom change detection strategy for some of the inputs.

The default change-detection algorithm looks for differences by comparing bound-property values by reference across change detection runs. You can use this hook to check for and respond to changes by some other means.

When the default change detector detects changes to the directive's input bindings, it invokes `ngOnChanges()` if supplied, regardless of whether you perform additional change detection. Typically, you should not use both [`DoCheck`](/api/core/DoCheck) and [`OnChanges`](/api/core/OnChanges) to respond to changes on the same input.

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to invoke it own change-detection cycle.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements DoCheck {
        ngDoCheck() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/api/router/ROUTER_OUTLET_DATA
----

```
    
      const ROUTER_OUTLET_DATA: InjectionToken<Signal<unknown>>;
    
    
```

## [Description](#description)

An [`InjectionToken`](/api/core/InjectionToken) provided by the [`RouterOutlet`](/api/router/RouterOutlet) and can be set using the `routerOutletData` input.

When unset, this value is `null` by default.

## [Usage Notes](#usage-notes)

To set the data from the template of the component with `router-outlet`:

```
<router-outlet [routerOutletData]="{name: 'Angular'}" />
```

To read the data in the routed component:

```
data = inject(ROUTER_OUTLET_DATA) as Signal<{name: string}>;
```

Jump to details

----
url: https://angular.dev/errors/NG02802
----

Angular produces this warning when code attempts to access an HTTP header that was not transferred from the server to the client by the `HttpTransferCache`.

By default, `HttpTransferCache` does not transfer any headers to avoid potential security issues or unnecessary data transfer. When a header is accessed on the client that wasn't included, Angular logs this warning to help you identify missing headers.

## [Fixing the error](#fixing-the-error)

To include specific headers in the transfer cache, use the `includeHeaders` option.

### [At the request level](#at-the-request-level)

Add the `transferCache` parameter to your HTTP request:

```
this.http.get('/api/data', {
  transferCache: {
    includeHeaders: ['cache-control', 'etag'],
  },
});
```

### [At the application level](#at-the-application-level)

Configure `includeHeaders` when providing the transfer cache:

```
provideClientHydration(
  withHttpTransferCacheOptions({
    includeHeaders: ['cache-control', 'etag'],
  }),
);
```

Only include headers that are necessary for your application logic. Be cautious with sensitive headers.

## [Debugging the error](#debugging-the-error)

The warning message includes the header name and the request URL. Check your code for where this header is being accessed and determine if it needs to be included in the transfer cache.

If the header is not needed on the client, consider refactoring your code to avoid accessing it.

----
url: https://angular.dev/guide/di/lightweight-injection-tokens
----

This page provides a conceptual overview of a dependency injection technique that is recommended for library developers. Designing your library with *lightweight injection tokens* helps optimize the bundle size of client applications that use your library.

You can manage the dependency structure among your components and injectable services to optimize bundle size by using tree-shakable providers. This normally ensures that if a provided component or service is never actually used by the application, the compiler can remove its code from the bundle.

Due to the way Angular stores injection tokens, it is possible that such an unused component or service can end up in the bundle anyway. This page describes a dependency injection design pattern that supports proper tree-shaking by using lightweight injection tokens.

The lightweight injection token design pattern is especially important for library developers. It ensures that when an application uses only some of your library's capabilities, the unused code can be eliminated from the client's application bundle.

When an application uses your library, there might be some services that your library supplies which the client application doesn't use. In this case, the application developer should expect that service to be tree-shaken, and not contribute to the size of the compiled application. Because the application developer cannot know about or remedy a tree-shaking problem in the library, it is the responsibility of the library developer to do so. To prevent the retention of unused components, your library should use the lightweight injection token design pattern.

## [When tokens are retained](#when-tokens-are-retained)

To better explain the condition under which token retention occurs, consider a library that provides a library-card component. This component contains a body and can contain an optional header:

```
<lib-card>
  <lib-header>…</lib-header>
</lib-card>
```

In a likely implementation, the `<lib-card>` component uses [`contentChild`](/api/core/contentChild) or [`contentChildren`](/api/core/contentChildren) to get `<lib-header>` and `<lib-body>`, as in the following:

```
import {Component, contentChild} from '@angular/core';

@Component({
  selector: 'lib-header',
  …,
})
class LibHeader {}

@Component({
  selector: 'lib-card',
  …,
})
class LibCard {
  readonly header = contentChild(LibHeader);
}
```

Because `<lib-header>` is optional, the element can appear in the template in its minimal form, `<lib-card />`. In this case, `<lib-header>` is not used and you would expect it to be tree-shaken, but that is not what happens. This is because `LibCard` actually contains two references to the `LibHeader`:

```
readonly header = contentChild(LibHeader);
```

* One of these references is in the *type position*-- that is, it specifies `LibHeader` as a type: `readonly header: Signal<LibHeader|undefined>`.
* The other reference is in the *value position*-- that is, `LibHeader` is the value passed into the [`contentChild`](/api/core/contentChild) function: `contentChild(LibHeader)`.

The compiler handles token references in these positions differently:

* The compiler erases *type position* references after conversion from TypeScript, so they have no impact on tree-shaking.
* The compiler must keep *value position* references at runtime, which **prevents** the component from being tree-shaken.

In the example, the compiler retains the `LibHeader` token that occurs in the value position. This prevents the referenced component from being tree-shaken, even if the application does not actually use `<lib-header>` anywhere. If `LibHeader` 's code, template, and styles combine to become too large, including it unnecessarily can significantly increase the size of the client application.

## [When to use the lightweight injection token pattern](#when-to-use-the-lightweight-injection-token-pattern)

The tree-shaking problem arises when a component is used as an injection token. There are two cases when that can happen:

* The token is used in the value position of a [content query](guide/components/queries#content-queries).
* The token is used with the `inject` function.

In the following example, both uses of the `CustomOther` token cause retention of `CustomOther`, preventing it from being tree-shaken when it is not used:

```
class App {
  private readonly other = inject(CustomOther, {optional: true});

  readonly header = contentChild(CustomOther);
}
```

Although tokens used only as type specifiers are removed when converted to JavaScript, all tokens used for dependency injection are needed at runtime. When using `inject(CustomOther)`, `CustomOther` is passed as a value argument. The token is now in a value position, which causes the tree-shaker to keep the reference.

**HELPFUL:** Libraries should use [tree-shakable providers](guide/di/defining-dependency-providers) for all services, providing dependencies at the root level rather than in components or modules.

## [Using lightweight injection tokens](#using-lightweight-injection-tokens)

The lightweight injection token design pattern consists of using a small abstract class as an injection token, and providing the actual implementation at a later stage. The abstract class is retained, not tree-shaken, but it is small and has no material impact on the application size.

The following example shows how this works for the `LibHeader`:

```
abstract class LibHeaderToken {}

@Component({
  selector: 'lib-header',
  providers: [{provide: LibHeaderToken, useExisting: LibHeader}],
  …,
})
class LibHeader extends LibHeaderToken {}

@Component({
  selector: 'lib-card',
  …,
})
class LibCard {
  readonly header = contentChild(LibHeaderToken);
}
```

In this example, the `LibCard` implementation no longer refers to `LibHeader` in either the type position or the value position. This lets full tree-shaking of `LibHeader` take place. The `LibHeaderToken` is retained, but it is only a class declaration, with no concrete implementation. It is small and does not materially impact the application size when retained after compilation.

Instead, `LibHeader` itself implements the abstract `LibHeaderToken` class. You can safely use that token as the provider in the component definition, allowing Angular to correctly inject the concrete type.

To summarize, the lightweight injection token pattern consists of the following:

1. A lightweight injection token that is represented as an abstract class.
2. A component definition that implements the abstract class.
3. Injection of the lightweight pattern, using [`contentChild`](/api/core/contentChild) or [`contentChildren`](/api/core/contentChildren).
4. A provider in the implementation of the lightweight injection token which associates the lightweight injection token with the implementation.

### [Use the lightweight injection token for API definition](#use-the-lightweight-injection-token-for-api-definition)

A component that injects a lightweight injection token might need to invoke a method in the injected class. The token is now an abstract class. Since the injectable component implements that class, you must also declare an abstract method in the abstract lightweight injection token class. The implementation of the method, with all its code overhead, resides in the injectable component that can be tree-shaken. This lets the parent communicate with the child, if it is present, in a type-safe manner.

For example, the `LibCard` now queries `LibHeaderToken` rather than `LibHeader`. The following example shows how the pattern lets `LibCard` communicate with the `LibHeader` without actually referring to `LibHeader`:

```
abstract class LibHeaderToken {
  abstract doSomething(): void;
}

@Component({
  selector: 'lib-header',
  providers: [{provide: LibHeaderToken, useExisting: LibHeader}],
})
class LibHeader extends LibHeaderToken {
  doSomething(): void {
    // Concrete implementation of `doSomething`
  }
}

@Component({
  selector: 'lib-card',
})
class LibCard implements AfterContentInit {
  readonly header = contentChild(LibHeaderToken);

  ngAfterContentInit(): void {
    if (this.header() !== undefined) {
      this.header()!.doSomething();
    }
  }
}
```

In this example, the parent queries the token to get the child component, and stores the resulting component reference if it is present. Before calling a method in the child, the parent component checks to see if the child component is present. If the child component has been tree-shaken, there is no runtime reference to it, and no call to its method.

### [Naming your lightweight injection token](#naming-your-lightweight-injection-token)

Lightweight injection tokens are only useful with components. The [Angular Style Guide](style-guide) suggests that you name components without the suffix [`Component`](/api/core/Component). The example `LibHeader` follows this convention.

You should maintain the relationship between the component and its token while still distinguishing between them. The recommended style is to use the component base name with the suffix `Token` to name your lightweight injection tokens: `LibHeaderToken`.

----
url: https://angular.dev/api/core/DebugEventListener
----

```
    
      class DebugEventListener {}
    
    
```

@paramname`string`

@paramcallback`Function`

@returns`DebugEventListener`

Jump to details

----
url: https://angular.dev/api/forms/signals/validateStandardSchema
----

```
function validateStandardSchema<
  TSchema,
  TModel extends IgnoreUnknownProperties<TSchema>,
>(
  path: SchemaPath<TModel> &
    ([TModel] extends [AbstractControl]
      ? CompatSchemaPath<TModel, Root>
      : SchemaPath<TModel, 1, Root>) &
    ([TModel] extends [AbstractControl]
      ? unknown
      : [TModel] extends [readonly any[]]
        ? unknown
        : TModel extends Record<string, any>
          ? { [K in keyof TModel]: MaybeSchemaPathTree<TModel[K], Child> }
          : unknown),
  schema: any,
): void;
```

@parampath`SchemaPath<TModel> & ([TModel] extends [AbstractControl] ? CompatSchemaPath<TModel, Root> : SchemaPath<TModel, 1, Root>) & ([TModel] extends [AbstractControl] ? unknown : [TModel] extends [readonly any[]] ? unknown : TModel extends Record<string, any> ? { [K in keyof TModel]: MaybeSchemaPathTree<TModel[K], Child>; } : unknown)`

The `FieldPath` to the field to validate.

@paramschema`any`

The standard schema compatible validator to use for validation, or a LogicFn that returns the schema.

@returns`void`

## [Description](#description)

Validates a field using a `StandardSchemaV1` compatible validator (e.g. a Zod validator).

See <https://github.com/standard-schema/standard-schema> for more about standard schema.

Jump to details

----
url: https://angular.dev/api/forms/signals/compat/SignalFormControl
----

```
    
      class SignalFormControl<T> extends AbstractControl {}
    
    
```

@paramvalue`T`

@paramschemaOrOptions`any`

@paramoptions`any`

@returns`SignalFormControl<T>`

Source FieldTree.

The raw signal driving the control value.

@paramvalue`any`

@paramoptions`ValueUpdateOptions | undefined`

@returns`void`

@paramvalue`any`

@paramoptions`ValueUpdateOptions | undefined`

@returns`void`

@paramfn`(value?: any, emitModelEvent?: boolean | undefined) => void`

@returns`void`

@paramfn`(isDisabled: boolean) => void`

@returns`void`

@returns`T`

@paramvalue`any`

@paramoptions`ValueUpdateOptions | undefined`

@returns`void`

@paramopts`{ onlySelf?: boolean | undefined; } | undefined`

@returns`void`

@paramopts`{ onlySelf?: boolean | undefined; } | undefined`

@returns`void`

@paramopts`{ onlySelf?: boolean | undefined; } | undefined`

@returns`void`

@paramopts`{ onlySelf?: boolean | undefined; } | undefined`

@returns`void`

@param\_opts`Object | undefined`

@returns`void`

@param\_opts`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; } | undefined`

@returns`void`

@param\_opts`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; } | undefined`

@returns`void`

@param\_validators`any`

@returns`void`

@param\_validators`any`

@returns`void`

@param\_validators`any`

@returns`void`

@param\_validators`any`

@returns`void`

@param\_validators`any`

@returns`void`

@param\_validators`any`

@returns`void`

@returns`void`

@returns`void`

@param\_errors`any`

@param\_opts`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

@param\_opts`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; } | undefined`

@returns`void`

## [Description](#description)

A `FormControl` that is backed by signal forms rules.

This class provides a bridge between Signal Forms and Reactive Forms, allowing signal-based controls to be used within a standard [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray).

A control could be created using signal forms, and integrated with an existing FormGroup propagating all the statuses and validity.

## [Usage Notes](#usage-notes)

### [Basic usage](#basic-usage)

```
const form = new FormGroup({
  // You can create SignalFormControl with signal form rules, and add it to a FormGroup.
  name: new SignalFormControl('Alice', p => {
    required(p);
  }),
  age: new FormControl(25),
});
```

In the template you can get the underlying `fieldTree` and bind it:

```
<form [formGroup]="form">
  <input [formField]="nameControl.fieldTree" />
  <input formControlName="age" />
</form>
```

Jump to details

----
url: https://angular.dev/api/animations/query
----

```
function query(
  selector: string,
  animation: AnimationMetadata | AnimationMetadata[],
  options?: AnimationQueryOptions | null,
): AnimationQueryMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramselector`string`

The element to query, or a set of elements that contain Angular-specific characteristics, specified with one or more of the following tokens.

* `query(":enter")` or `query(":leave")` : Query for newly inserted/removed elements (not all elements can be queried via these tokens, see [Entering and Leaving Elements](#entering-and-leaving-elements))
* `query(":animating")` : Query all currently animating elements.
* `query("@triggerName")` : Query elements that contain an animation trigger.
* `query("@*")` : Query all elements that contain an animation triggers.
* `query(":self")` : Include the current element into the animation sequence.

@paramanimation`AnimationMetadata | AnimationMetadata[]`

One or more animation steps to apply to the queried element or elements. An array is treated as an animation sequence.

@paramoptions`AnimationQueryOptions | null`

An options object. Use the 'limit' field to limit the total number of items to collect.

@returns`AnimationQueryMetadata`

## [Usage Notes](#usage-notes)

### [Multiple Tokens](#multiple-tokens)

Tokens can be merged into a combined query selector string. For example:

```
query('@subTrigger, :self, .record:enter, .record:leave', [...])
```

The [`query()`](/api/animations/query) function collects multiple elements and works internally by using `element.querySelectorAll`. Use the `limit` field of an options object to limit the total number of items to be collected. For example:

```
query('div', [
  animate(...),
  animate(...)
], { limit: 1 })
```

By default, throws an error when zero items are found. Set the `optional` flag to ignore this error. For example:

```
query('.some-element-that-may-not-be-there', [
  animate(...),
  animate(...)
], { optional: true })
```

### [Entering and Leaving Elements](#entering-and-leaving-elements)

Not all elements can be queried via the `:enter` and `:leave` tokens, the only ones that can are those that Angular assumes can enter/leave based on their own logic (if their insertion/removal is simply a consequence of that of their parent they should be queried via a different token in their parent's `:enter`/`:leave` transitions).

The only elements Angular assumes can enter/leave based on their own logic (thus the only ones that can be queried via the `:enter` and `:leave` tokens) are:

* Those inserted dynamically (via `ViewContainerRef`)
* Those that have a structural directive (which, under the hood, are a subset of the above ones)

Note that elements will be successfully queried via `:enter`/`:leave` even if their insertion/removal is not done manually via `ViewContainerRef`or caused by their structural directive (e.g. they enter/exit alongside their parent).

There is an exception to what previously mentioned, besides elements entering/leaving based on their own logic, elements with an animation trigger can always be queried via `:leave` when their parent is also leaving.

### [Usage Example](#usage-example)

The following example queries for inner elements and animates them individually using [`animate()`](/api/animations/animate).

```
@Component({
  selector: 'inner',
  template: `
    <div [@queryAnimation]="exp">
      <h1>Title</h1>
      <div class="content">
        Blah blah blah
      </div>
    </div>
  `,
  animations: [
   trigger('queryAnimation', [
     transition('* => goAnimate', [
       // hide the inner elements
       query('h1', style({ opacity: 0 })),
       query('.content', style({ opacity: 0 })),

       // animate the inner elements in, one by one
       query('h1', animate(1000, style({ opacity: 1 }))),
       query('.content', animate(1000, style({ opacity: 1 }))),
     ])
   ])
 ]
})
class Cmp {
  exp = '';

  goAnimate() {
    this.exp = 'goAnimate';
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/HOST_TAG_NAME
----

```
    
      const HOST_TAG_NAME: InjectionToken<string>;
    
    
```

## [Usage Notes](#usage-notes)

### [Injecting a tag name that is known to exist](#injecting-a-tag-name-that-is-known-to-exist)

```
@Directive()
class MyDir {
  tagName: string = inject(HOST_TAG_NAME);
}
```

### [Optionally injecting a tag name](#optionally-injecting-a-tag-name)

```
@Directive()
class MyDir {
  tagName: string | null = inject(HOST_TAG_NAME, {optional: true});
}
```

Jump to details

----
url: https://angular.dev/api/forms/ControlConfig
----

```
    
      type ControlConfig<T> = [  T | FormControlState<T>,  (ValidatorFn | ValidatorFn[])?,  (AsyncValidatorFn | AsyncValidatorFn[])?,]
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MinValidationError
----

```
    
      class MinValidationError extends BaseNgValidationError {}
    
    
```

@parammin`number`

@paramoptions`ValidationErrorOptions | undefined`

@returns`MinValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/router/GuardResult
----

```
    
      type GuardResult = boolean | UrlTree | RedirectCommand
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpHeaders
----

```
    
      class HttpHeaders {}
    
    
```

Constructs a new HTTP header object with the given values.

@paramheaders`string | { [name: string]: string | number | (string | number)[]; } | Headers | undefined`

@returns`HttpHeaders`

Checks for existence of a given header.

@paramname`string`

The header name to check for existence.

@returns`boolean`

True if the header exists, false otherwise.

Retrieves the first value of a given header.

@paramname`string`

The header name.

@returns`string | null`

The value string if the header exists, null otherwise

Retrieves the names of the headers.

@returns`string[]`

A list of header names.

Retrieves a list of values for a given header.

@paramname`string`

The header name from which to retrieve values.

@returns`string[] | null`

A string of values if the header exists, null otherwise.

Appends a new value to the existing set of values for a header and returns them in a clone of the original instance.

@paramname`string`

The header name for which to append the values.

@paramvalue`string | string[]`

The value to append.

@returns`HttpHeaders`

A clone of the HTTP headers object with the value appended to the given header.

Sets or modifies a value for a given header in a clone of the original instance. If the header already exists, its value is replaced with the given value in the returned object.

@paramname`string`

The header name.

@paramvalue`string | string[]`

The value or values to set or override for the given header.

@returns`HttpHeaders`

A clone of the HTTP headers object with the newly set header value.

Deletes values for a given header in a clone of the original instance.

@paramname`string`

The header name.

@paramvalue`string | string[] | undefined`

The value or values to delete for the given header.

@returns`HttpHeaders`

A clone of the HTTP headers object with the given value deleted.

Jump to details

----
url: https://angular.dev/api/router/mapToCanActivate
----

```
function mapToCanActivate(providers: Type<CanActivate>[]): CanActivateFn[];
```

@paramproviders`Type<CanActivate>[]`

@returns`CanActivateFn[]`

## [Description](#description)

Maps an array of injectable classes with canActivate functions to an array of equivalent [`CanActivateFn`](/api/router/CanActivateFn) for use in a [`Route`](/api/router/Route) definition.

Usage

```
@Injectable({providedIn: 'root'})
export class AdminGuard {
  canActivate() {
    return true;
  }
}

const route: Route = {
  path: 'admin',
  canActivate: mapToCanActivate([AdminGuard]),
};
```

Jump to details

----
url: https://angular.dev/guide/forms/signals/field-state-management
----

Signal Forms' field state allows you to react to user interactions by providing reactive signals for validation status (such as `valid`, `invalid`, `errors`), interaction tracking (such as `touched`, `dirty`), and availability (such as `disabled`, `hidden`).

## [Understanding field state](#understanding-field-state)

When you create a form with the [`form()`](api/forms/signals/form) function, it returns a **field tree** - an object structure that mirrors your form model. Each field in the tree is accessible via dot notation (like [`form.email`](api/forms/signals/FieldTree)).

### [Accessing field state](#accessing-field-state)

When you call any field in the field tree as a function (like [`form.email()`](api/forms/signals/FieldState)), it returns a [`FieldState`](/api/forms/signals/FieldState) object containing reactive signals that track the field's validation, interaction, and availability state. For example, the `invalid()` signal tells you whether the field has validation errors:

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, email} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <input type="email" [formField]="registrationForm.email" />

    @if (registrationForm.email().invalid()) {
      <p class="error">Email has validation errors:</p>
      <ul>
        @for (error of registrationForm.email().errors(); track error) {
          <li>{{ error.message }}</li>
        }
      </ul>
    }
  `,
})
export class Registration {
  registrationModel = signal({
    email: '',
    password: '',
  });

  registrationForm = form(this.registrationModel, (schemaPath) => {
    required(schemaPath.email, {message: 'Email is required'});
    email(schemaPath.email, {message: 'Enter a valid email address'});
  });
}
```

In this example, the template checks `registrationForm.email().invalid()` to determine whether to display an error message.

### [Field state signals](#field-state-signals)

The most commonly used signal is `value()`, a [`WritableSignal`](/api/core/WritableSignal) that provides access to the field's current value:

```
const emailValue = registrationForm.email().value();
console.log(emailValue); // Current email string
```

Beyond `value()`, field state includes signals for validation, interaction tracking, and availability control:

| Category                                | Signal                                      | Description                                                                       |
| --------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------- |
| **[Validation](#validation-state)**     | `valid()`                                   | Field passes all validation rules and has no pending validators                   |
|                                         | `invalid()`                                 | Field has validation errors                                                       |
|                                         | `errors()`                                  | Array of validation error objects                                                 |
|                                         | `pending()`                                 | Async validation in progress                                                      |
| **[Interaction](#interaction-state)**   | `touched()`                                 | User has focused and blurred the field (if interactive)                           |
|                                         | `dirty()`                                   | User has modified the field (if interactive), even if value matches initial state |
| **[Availability](#availability-state)** | [`disabled()`](/api/forms/signals/disabled) | Field is disabled and doesn't affect parent form state                            |
|                                         | [`hidden()`](/api/forms/signals/hidden)     | Indicates field should be hidden; visibility in template is controlled with `@if` |
|                                         | [`readonly()`](/api/forms/signals/readonly) | Field is readonly and doesn't affect parent form state                            |

These signals enable you to build responsive form user experiences that react to user behavior. The sections below explore each category in detail.

## [Validation state](#validation-state)

Validation state signals tell you whether a field is valid and what errors it contains.

**NOTE:** This guide focuses on **using** validation state in your templates and logic (such as reading `valid()`, `invalid()`, `errors()` to display feedback). For information on **defining** validation rules and creating custom validators, see the [Validation guide](guide/forms/signals/validation).

### [Checking validity](#checking-validity)

Use `valid()` and `invalid()` to check validation status:

```
@Component({
  template: `
    <input type="email" [formField]="loginForm.email" />

    @if (loginForm.email().invalid()) {
      <p class="error">Email is invalid</p>
    }
    @if (loginForm.email().valid()) {
      <p class="success">Email looks good</p>
    }
  `,
})
export class Login {
  loginModel = signal({email: '', password: ''});
  loginForm = form(this.loginModel);
}
```

| Signal      | Returns `true` when                                             |
| ----------- | --------------------------------------------------------------- |
| `valid()`   | Field passes all validation rules and has no pending validators |
| `invalid()` | Field has validation errors                                     |

When checking validity in code, use `invalid()` instead of `!valid()` if you want to distinguish between "has errors" and "validation pending." The reason for this is that both `valid()` and `invalid()` can be `false` simultaneously when async validation is pending because the field isn't valid yet since validation not complete and is also isn't invalid since no errors have been found yet.

### [Reading validation errors](#reading-validation-errors)

Access the array of validation errors with `errors()`, or an individual error with `getError()`. Each error object contains:

| Property    | Description                                                                           |
| ----------- | ------------------------------------------------------------------------------------- |
| `kind`      | The validation rule that failed (such as "required" or "email")                       |
| `message`   | Optional human-readable error message                                                 |
| `fieldTree` | Reference to the [`FieldTree`](/api/forms/signals/FieldTree) where the error occurred |

**NOTE:** The `message` property is optional. Validators can provide custom error messages, but if not specified, you may need to map error `kind` values to your own messages.

Here's an example of how to display errors in your template by an array of validation errors:

```
@Component({
  template: `
    <input type="email" [formField]="loginForm.email" />

    @if (loginForm.email().errors().length > 0) {
      <div class="errors">
        @for (error of loginForm.email().errors(); track error) {
          <p>{{ error.message }}</p>
        }
      </div>
    }
  `
})
```

This approach loops through all errors for a field, displaying each error message to the user.

Here's an example of how to display an error in your template with `getError()`:

```
@Component({
  template: `
    <input type="email" [formField]="loginForm.email" />

    @if (loginForm.email().getError('required'); as error) {
      <div class="errors">
        <p>{{ error.message }}</p>
      </div>
    }
  `
})
```

`getError()` accepts any string for an error's `kind`, and recognizes build-in validation error kinds (e.g., "required", "email", "minLength").

### [Pending validation](#pending-validation)

The `pending()` signal indicates async validation is in progress:

```
@Component({
  template: `
    <input type="email" [formField]="signupForm.email" />

    @if (signupForm.email().pending()) {
      <p>Checking if email is available...</p>
    }

    @if (signupForm.email().invalid() && !signupForm.email().pending()) {
      <p>Email is already taken</p>
    }
  `
})
```

This signal enables you to show loading states while async validation executes.

## [Interaction state](#interaction-state)

Interaction state tracks whether users have interacted with fields, enabling patterns like "show errors only after the user has touched a field."

### [Touched state](#touched-state)

The `touched()` signal tracks whether a user has focused and then blurred a field, or whether the field has been marked as touched programmatically. Only interactive fields can become touched; hidden, disabled, and readonly fields don't become touched from user interactions or `markAsTouched()`.

When you need a section-level action to reveal validation errors within that section, call `markAsTouched()` on the section field. The default value of `skipDescendants` is `false`, so the call marks the section field and each descendant field as touched.

For example, a checkout flow can validate the shipping section before allowing the user to continue to the next step:

```
import {Component, signal} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';

@Component({
  selector: 'app-checkout-shipping',
  imports: [FormField],
  template: `
    <label>
      Name
      <input [formField]="checkoutForm.shipping.name" />
    </label>
    @if (checkoutForm.shipping.name().touched() && checkoutForm.shipping.name().invalid()) {
      <p>{{ checkoutForm.shipping.name().errors()[0].message }}</p>
    }

    <label>
      Address
      <input [formField]="checkoutForm.shipping.address" />
    </label>
    @if (checkoutForm.shipping.address().touched() && checkoutForm.shipping.address().invalid()) {
      <p>{{ checkoutForm.shipping.address().errors()[0].message }}</p>
    }

    <button type="button" (click)="continueToPayment()">Continue</button>

    @if (showPayment() && checkoutForm.shipping().valid()) {
      <p>Ready for payment.</p>
    }
  `,
})
export class CheckoutShipping {
  checkoutModel = signal({
    shipping: {
      name: '',
      address: '',
    },
  });

  showPayment = signal(false);

  checkoutForm = form(this.checkoutModel, (schemaPath) => {
    required(schemaPath.shipping.name, {message: 'Enter a name'});
    required(schemaPath.shipping.address, {message: 'Enter an address'});
  });

  continueToPayment() {
    this.checkoutForm.shipping().markAsTouched();

    if (this.checkoutForm.shipping().invalid()) {
      return;
    }

    this.showPayment.set(true);
  }
}
```

When `continueToPayment()` calls `markAsTouched()` on `checkoutForm.shipping()`, it uses the default `skipDescendants: false` behavior. Angular marks `shipping`, `shipping.name`, and `shipping.address` as touched, so the child `touched() && invalid()` error messages become visible before the whole form is submitted.

**NOTE:** Pass `{skipDescendants: true}` only when the field receiving the call should become touched without changing the touched state of its descendants.

### [Dirty state](#dirty-state)

Forms often need to detect whether data has actually changed - for example, to warn users about unsaved changes or to enable a save button only when necessary. The `dirty()` signal tracks whether the user has modified the field.

The `dirty()` signal becomes `true` when the user modifies an interactive field's value, and remains `true` even if the value is changed back to match the initial value:

```
@Component({
  template: `
    <form novalidate>
      <input [formField]="profileForm.name" />
      <input [formField]="profileForm.bio" />

      @if (profileForm().dirty()) {
        <p class="warning">You have unsaved changes</p>
      }
    </form>
  `,
})
export class Profile {
  profileModel = signal({name: 'Alice', bio: 'Developer'});
  profileForm = form(this.profileModel);
}
```

Use `dirty()` for "unsaved changes" warnings or to enable save buttons only when data has changed.

### [Touched vs dirty](#touched-vs-dirty)

These signals track different kinds of interaction state:

| Signal      | When it becomes true                                                                                                            |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `touched()` | User has focused and blurred an interactive field, or the field was marked as touched programmatically                          |
| `dirty()`   | User has modified an interactive field (even if they never blurred it, and even if the current value matches the initial value) |

A field can be in different combinations:

| State                  | Scenario                                                  |
| ---------------------- | --------------------------------------------------------- |
| Touched but not dirty  | User focused and blurred the field but made no changes    |
| Both touched and dirty | User focused the field, changed the value, and blurred it |

**NOTE:** Hidden, disabled, and readonly fields are non-interactive - they don't become touched or dirty from user interactions.

## [Availability state](#availability-state)

Availability state signals control whether fields are interactive, editable, or visible. Disabled, hidden, and readonly fields are non-interactive. They don't affect whether their parent form is valid, touched, or dirty.

### [Disabled fields](#disabled-fields)

The [`disabled()`](/api/forms/signals/disabled) signal indicates whether a field accepts user input. Disabled fields appear in the UI but users cannot interact with them.

```
import {Component, signal} from '@angular/core';
import {form, FormField, disabled} from '@angular/forms/signals';

@Component({
  selector: 'app-order',
  imports: [FormField],
  // TIP: The `[formField]` directive automatically binds the `disabled` attribute based
  // on the field's `disabled()` state, so you don't need to manually add `[disabled]="field().disabled()"`
  template: `
    <input [formField]="orderForm.couponCode" />

    @if (orderForm.couponCode().disabled()) {
      <p class="info">Coupon code is only available for orders over $50</p>
    }
  `,
})
export class Order {
  orderModel = signal({
    total: 25,
    couponCode: '',
  });

  orderForm = form(this.orderModel, (schemaPath) => {
    disabled(schemaPath.couponCode, {when: ({valueOf}) => valueOf(schemaPath.total) < 50});
  });
}
```

In this example, we use `valueOf(schemaPath.total)` to check the value of the `total` field to determine whether `couponCode` should be disabled.

**NOTE:** The schema callback parameter (`schemaPath` in these examples) is a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) object that provides paths to all fields in your form. You can name this parameter anything you like.

When defining rules like [`disabled()`](/api/forms/signals/disabled), [`hidden()`](/api/forms/signals/hidden), or [`readonly()`](/api/forms/signals/readonly), the `when` function receives a [`FieldContext`](/api/forms/signals/FieldContext) object that is typically destructured (such as `({valueOf})`). Two methods commonly used in validation rules are:

* `valueOf(schemaPath.otherField)` - Read the value of another field in the form
* `value()` - A signal containing the value of the field the rule is applied to

Disabled fields don't contribute to the parent form's validation state. Even if a disabled field would be invalid, the parent form can still be valid. The [`disabled()`](/api/forms/signals/disabled) state affects interactivity and validation, but does not change the field's value.

### [Hidden fields](#hidden-fields)

The [`hidden()`](/api/forms/signals/hidden) signal indicates whether a field is conditionally hidden. Use [`hidden()`](/api/forms/signals/hidden) with `@if` to show or hide fields based on conditions:

```
import {Component, signal} from '@angular/core';
import {form, FormField, hidden} from '@angular/forms/signals';

@Component({
  selector: 'app-profile',
  imports: [FormField],
  template: `
    <label>
      <input type="checkbox" [formField]="profileForm.isPublic" />
      Make profile public
    </label>

    @if (!profileForm.publicUrl().hidden()) {
      <label>
        Public URL
        <input [formField]="profileForm.publicUrl" />
      </label>
    }
  `,
})
export class Profile {
  profileModel = signal({
    isPublic: false,
    publicUrl: '',
  });

  profileForm = form(this.profileModel, (schemaPath) => {
    hidden(schemaPath.publicUrl, {when: ({valueOf}) => !valueOf(schemaPath.isPublic)});
  });
}
```

Hidden fields don't participate in validation. If a required field is hidden, it won't prevent form submission. The [`hidden()`](/api/forms/signals/hidden) state affects availability and validation, but does not change the field's value.

### [Readonly fields](#readonly-fields)

The [`readonly()`](/api/forms/signals/readonly) signal indicates whether a field is readonly. Readonly fields display their value but users cannot edit them:

```
import {Component, signal} from '@angular/core';
import {form, FormField, readonly} from '@angular/forms/signals';

@Component({
  selector: 'app-account',
  imports: [FormField],
  template: `
    <label>
      Username (cannot be changed)
      <input [formField]="accountForm.username" />
    </label>

    <label>
      Email
      <input [formField]="accountForm.email" />
    </label>
  `,
})
export class Account {
  accountModel = signal({
    username: 'johndoe',
    email: 'john@example.com',
  });

  accountForm = form(this.accountModel, (schemaPath) => {
    readonly(schemaPath.username);
  });
}
```

**NOTE:** The `[formField]` directive automatically binds the `readonly` attribute based on the field's [`readonly()`](/api/forms/signals/readonly) state, so you don't need to manually add `[readonly]="field().readonly()"`.

Like disabled and hidden fields, readonly fields are non-interactive and don't affect parent form state. The [`readonly()`](/api/forms/signals/readonly) state affects editability and validation, but does not change the field's value.

### [When to use each](#when-to-use-each)

| State                                       | Use when                                                            | User can see it | User can interact | Contributes to validation |
| ------------------------------------------- | ------------------------------------------------------------------- | --------------- | ----------------- | ------------------------- |
| [`disabled()`](/api/forms/signals/disabled) | Field temporarily unavailable (such as based on other field values) | Yes             | No                | No                        |
| [`hidden()`](/api/forms/signals/hidden)     | Field not relevant in current context                               | No (with @if)   | No                | No                        |
| [`readonly()`](/api/forms/signals/readonly) | Value should be visible but not editable                            | Yes             | No                | No                        |

## [Form-level state](#form-level-state)

The root form is also a field in the field tree. When you call it as a function, it also returns a [`FieldState`](/api/forms/signals/FieldState) object that aggregates the state of all child fields.

### [Accessing form state](#accessing-form-state)

```
@Component({
  template: `
    <form novalidate>
      <input [formField]="loginForm.email" />
      <input [formField]="loginForm.password" />

      <button [disabled]="!loginForm().valid()">Sign In</button>
    </form>
  `,
})
export class Login {
  loginModel = signal({email: '', password: ''});
  loginForm = form(this.loginModel);
}
```

In this example, the form is valid only when all child fields are valid. This allows you to enable/disable submit buttons based on overall form validity.

### [Form-level signals](#form-level-signals)

Because the root form is a field, it has the same signals (such as `valid()`, `invalid()`, `touched()`, `dirty()`, etc.).

| Signal      | Form-level behavior                                            |
| ----------- | -------------------------------------------------------------- |
| `valid()`   | All interactive fields are valid and no validators are pending |
| `invalid()` | At least one interactive field has validation errors           |
| `pending()` | At least one interactive field has pending async validation    |
| `touched()` | The form, or at least one interactive descendant, is touched   |
| `dirty()`   | User has modified at least one interactive field               |

### [When to use form-level vs field-level](#when-to-use-form-level-vs-field-level)

**Use form-level state for:**

* Submit button enabled/disabled state
* "Save" button state
* Overall form validity checks
* Unsaved changes warnings

**Use field-level state for:**

* Individual field error messages
* Field-specific styling
* Per-field validation feedback
* Conditional field availability

## [State propagation](#state-propagation)

Field state propagates from child fields up through parent field groups to the root form.

### [How child state affects parent forms](#how-child-state-affects-parent-forms)

When a child field becomes invalid, its parent field group becomes invalid, and so does the root form. When a child becomes touched or dirty, the parent field group and root form reflect that change. This aggregation allows you to check validity at any level - field or entire form.

```
const userModel = signal({
  profile: {
    firstName: '',
    lastName: '',
  },
  address: {
    street: '',
    city: '',
  },
});

const userForm = form(userModel);

// If firstName is invalid, profile is invalid
userForm.profile.firstName().invalid() === true;
// → userForm.profile().invalid() === true
// → userForm().invalid() === true
```

### [Hidden, disabled, and readonly fields](#hidden-disabled-and-readonly-fields)

Hidden, disabled, and readonly fields are non-interactive and don't affect parent form state:

```
const orderModel = signal({
  customerName: '',
  requiresShipping: false,
  shippingAddress: '',
});

const orderForm = form(orderModel, (schemaPath) => {
  hidden(schemaPath.shippingAddress, {when: ({valueOf}) => !valueOf(schemaPath.requiresShipping)});
});
```

In this example, when `shippingAddress` is hidden, it doesn't affect form validity. As a result, even if `shippingAddress` is empty and required, the form can be valid.

This behavior prevents hidden, disabled, or readonly fields from blocking form submission or affecting validation, touched, and dirty state.

## [Using state in templates](#using-state-in-templates)

Field state signals integrate seamlessly with Angular templates, enabling reactive form user experiences without manual event handling.

### [Conditional error display](#conditional-error-display)

Show errors only after a user has interacted with a field:

```
import {Component, signal} from '@angular/core';
import {form, FormField, email} from '@angular/forms/signals';

@Component({
  selector: 'app-signup',
  imports: [FormField],
  template: `
    <label>
      Email
      <input type="email" [formField]="signupForm.email" />
    </label>

    @if (signupForm.email().touched() && signupForm.email().invalid()) {
      <p class="error">{{ signupForm.email().errors()[0].message }}</p>
    }
  `,
})
export class Signup {
  signupModel = signal({email: '', password: ''});

  signupForm = form(this.signupModel, (schemaPath) => {
    email(schemaPath.email);
  });
}
```

This pattern prevents showing errors before users have had a chance to interact with the field. Errors appear only after the user has focused and then left the field.

### [Conditional field availability](#conditional-field-availability)

Use the [`hidden()`](/api/forms/signals/hidden) signal with `@if` to show or hide fields conditionally:

```
import {Component, signal} from '@angular/core';
import {form, FormField, hidden} from '@angular/forms/signals';

@Component({
  selector: 'app-order',
  imports: [FormField],
  template: `
    <label>
      <input type="checkbox" [formField]="orderForm.requiresShipping" />
      Requires shipping
    </label>

    @if (!orderForm.shippingAddress().hidden()) {
      <label>
        Shipping Address
        <input [formField]="orderForm.shippingAddress" />
      </label>
    }
  `,
})
export class Order {
  orderModel = signal({
    requiresShipping: false,
    shippingAddress: '',
  });

  orderForm = form(this.orderModel, (schemaPath) => {
    hidden(schemaPath.shippingAddress, {
      when: ({valueOf}) => !valueOf(schemaPath.requiresShipping),
    });
  });
}
```

Hidden fields don't participate in validation, allowing the form to be submitted even if the hidden field would otherwise be invalid.

### [Tracking values for array fields](#tracking-values-for-array-fields)

In signal forms, a `@for` block over a set of fields should be tracked by field identity.

```
@Component({
  imports: [FormField],
  template: `
    @for (field of form.emails; track field) {
      <input [formField]="field" />
    }
  `,
})
export class App {
  formModel = signal({emails: ['john.doe@mail.com', 'max.musterman@mail.com']});
  form = form(this.formModel);
}
```

The forms system is already tracking the model values within the array and maintaining a stable identity of the fields it creates automatically.

When an item changes, it may represent a new logical entity even if some of its properties look the same. Tracking by identity ensures the framework treats it as a distinct item rather than reusing existing UI elements. This prevents stateful elements, like form inputs, from being incorrectly shared and keeps bindings aligned with the correct part of the model.

## [Using field state in component logic](#using-field-state-in-component-logic)

Field state signals work with Angular's reactive primitives like [`computed()`](/api/core/computed) and [`effect()`](/api/core/effect) for advanced form logic.

### [Validation checks before submission](#validation-checks-before-submission)

Check form validity in component methods:

```
export class Registration {
  registrationModel = signal({
    username: '',
    email: '',
    password: '',
  });

  registrationForm = form(this.registrationModel);

  async onSubmit() {
    // Wait for any pending async validation
    if (this.registrationForm().pending()) {
      console.log('Waiting for validation...');
      return;
    }

    // Guard against invalid submissions
    if (this.registrationForm().invalid()) {
      console.error('Form is invalid');
      return;
    }

    const data = this.registrationModel();
    await this.api.register(data);
  }
}
```

This ensures only valid, fully-validated data reaches your API.

### [Derived state with computed](#derived-state-with-computed)

Create computed signals based on field state to automatically update when the underlying field state changes:

```
export class Password {
  passwordModel = signal({password: '', confirmPassword: ''});
  passwordForm = form(this.passwordModel);

  // Compute password strength indicator
  passwordStrength = computed(() => {
    const password = this.passwordForm.password().value();
    if (password.length < 8) return 'weak';
    if (password.length < 12) return 'medium';
    return 'strong';
  });

  // Check if all required fields are filled
  allFieldsFilled = computed(() => {
    return (
      this.passwordForm.password().value().length > 0 &&
      this.passwordForm.confirmPassword().value().length > 0
    );
  });
}
```

### [Programmatic state changes](#programmatic-state-changes)

While field state typically updates through user interactions (typing, focusing, blurring), you sometimes need to control it programmatically. Common scenarios include form submission and resetting forms.

#### [Form submission](#form-submission)

Signal Forms provides a [`FormRoot`](/api/forms/signals/FormRoot) directive that simplifies form submission. It automatically prevents the default browser form submission behavior and sets the `novalidate` attribute on the `<form>` element.

```
import {FormField, FormRoot} from '@angular/forms/signals';

@Component({
  imports: [FormRoot, FormField],
  template: `
    <form [formRoot]="registrationForm">
      <input [formField]="registrationForm.username" />
      <input type="email" [formField]="registrationForm.email" />
      <input type="password" [formField]="registrationForm.password" />

      <button type="submit">Register</button>
    </form>
  `,
})
export class Registration {
  registrationModel = signal({username: '', email: '', password: ''});

  registrationForm = form(
    this.registrationModel,
    (schemaPath) => {
      required(schemaPath.username);
      email(schemaPath.email);
      required(schemaPath.password);
    },
    {
      submission: {
        action: async () => this.submitToServer(),
      },
    },
  );

  private submitToServer() {
    // Send data to server
  }
}
```

When you use [`FormRoot`](/api/forms/signals/FormRoot), submitting the form automatically calls the [`submit()`](/api/forms/signals/submit) function, which marks all fields as touched (revealing validation errors) and executes your `action` callback if the form is valid.

You can also submit a form manually, without using the directive, by calling `submit(this.registrationForm)`. When explicitly calling the [`submit`](/api/forms/signals/submit) function like this, you can pass a [`FormSubmitOptions`](/api/forms/signals/FormSubmitOptions) to override the default `submission` logic for the form: `submit(this.registrationForm, {action: () => /* ... */ })`.

#### [Resetting forms after submission](#resetting-forms-after-submission)

After successfully submitting a form, you may want to return it to its initial state - clearing both user interaction history and field values. The `reset()` method clears the touched and dirty flags. You can also pass an optional value to `reset()` to update the model data:

```
export class Contact {
  private readonly INITIAL_MODEL = {name: '', email: '', message: ''};
  contactModel = signal({...this.INITIAL_MODEL});
  contactForm = form(this.contactModel, {
    submission: {
      action: async (f) => {
        await this.api.sendMessage(this.contactModel());
        // Clear interaction state (touched, dirty) and reset to initial values
        f().reset({...this.INITIAL_MODEL});
      },
    },
  });
}
```

This ensures the form is ready for new input without showing stale error messages or dirty state indicators.

## [Styling based on validation state](#styling-based-on-validation-state)

You can apply custom styles to your form by binding CSS classes based on the validation state:

```
import {Component, signal} from '@angular/core';
import {form, FormField, email} from '@angular/forms/signals';

@Component({
  imports: [FormField],
  template: `
    <input
      type="email"
      [formField]="form.email"
      [class.is-invalid]="form.email().touched() && form.email().invalid()"
      [class.is-valid]="form.email().touched() && form.email().valid()"
    />
  `,
  styles: `
    input.is-invalid {
      border: 2px solid red;
      background-color: white;
    }

    input.is-valid {
      border: 2px solid green;
    }
  `,
})
export class StyleExample {
  model = signal({email: ''});

  form = form(this.model, (schemaPath) => {
    email(schemaPath.email);
  });
}
```

Checking both `touched()` and validation state ensures styles only appear after the user has interacted with the field.

## [Focus a form control bound to a form field](#focus-a-form-control-bound-to-a-form-field)

Angular Signal Forms provide a `focusBoundControl()` method on field state that lets you programmatically move [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) to the form control associated with a given form field.

A common use case is improving accessibility on form submission: when a form is invalid, display error messages and automatically move focus to the first invalid field, guiding the user to correct it.

### [Basic usage](#basic-usage)

Given a registration form:

```
@Component({/* ... */})
export class Registration {
  registrationModel = signal({username: '', email: '', password: ''});
  registrationForm = form(this.registrationModel, (schemaPath) => {
    required(schemaPath.username);
    email(schemaPath.email);
    required(schemaPath.password);
  });
}
```

To move focus to the control bound to the `email` field:

```
registrationForm.email().focusBoundControl();
```

### [Preventing scroll](#preventing-scroll)

If the target control is outside the viewport and you want to focus it without triggering a scroll, you can set the [preventScroll](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#preventscroll) option to `true` when calling the `focusBoundControl()` method.

```
registrationForm.email().focusBoundControl({preventScroll: true});
```

### [Focusing the first invalid field on submission](#focusing-the-first-invalid-field-on-submission)

Use `errorSummary()` to locate the first invalid field and focus it when the user submits the form with errors:

```
onSubmit() {
  const firstError = this.registrationForm().errorSummary()[0];
  if (firstError?.fieldTree) {
    firstError.fieldTree().focusBoundControl();
  } else {
    // proceed with submission
  }
}
```

### [Custom controls](#custom-controls)

By default, calling `focusBoundControl()` on a custom control has no effect because a custom control can contain multiple native inputs. For example, a date picker can contain separate day, month, and year fields. As a result, Angular cannot determine which element should receive focus or what action to perform.

To support programmatic focus in a custom control, implement a `focus()` method. When `focusBoundControl()` is called on the field state associated with a custom control, Angular calls the control's `focus()` method if one is present.

Consider a custom password input:

```
<div class="password-block">
  <input type="password" #passwordCtrl [value]="value()" (input)="value.set($event.target.value)" />
</div>
```

```
@Component({/* ... */})
export class PasswordInput implements FormValueControl<string> {
  readonly value = model<string>('');
  readonly passwordCtrl = viewChild.required<ElementRef<HTMLInputElement>>('passwordCtrl');

  // Called automatically when focusBoundControl() is invoked
  // on the field state associated with this custom control
  focus(): void {
    this.passwordCtrl().nativeElement.focus();
  }
}
```

## [Next steps](#next-steps)

This guide covered validation and availability status handling, interaction tracking and field state propagation. Related guides explore other aspects of Signal Forms:

----
url: https://angular.dev/guide/drag-drop
----

## [Overview](#overview)

This page describes the drag and drop directives which let you quickly create drag and drop interfaces with the following:

* Free dragging
* Create a list of reorderable draggable elements
* Transfer draggable elements between lists
* Dragging animations
* Lock draggable elements along an axis or element
* Add custom drag handles
* Add previews on drag
* Add custom drag placeholder

For the full API reference, please see the [Angular CDK's drag and drop API reference page](api#angular_cdk_drag-drop).

## [Before you start](#before-you-start)

### [CDK Installation](#cdk-installation)

The [Component Dev Kit (CDK)](https://material.angular.dev/cdk/categories) is a set of behavior primitives for building components. To use the drag and drop directives, first install `@angular/cdk` from npm. You can do this from your terminal using Angular CLI:

```
ng add @angular/cdk
```

### [Importing drag and drop](#importing-drag-and-drop)

To use drag and drop, import what you need from the directives in your component.

```
import {Component} from '@angular/core';
import {CdkDrag} from '@angular/cdk/drag-drop';

@Component({
  selector: 'drag-drop-example',
  templateUrl: 'drag-drop-example.html',
  imports: [CdkDrag],
})
export class DragDropExample {}
```

## [Create draggable elements](#create-draggable-elements)

You can make any element draggable by adding the `cdkDrag` directive. By default, all draggable elements support free dragging.

### app.html

```
<div class="example-box" cdkDrag>Drag me around</div>
```

### app.ts

```
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Basic Drag&Drop
 */
@Component({
  selector: 'cdk-drag-drop-overview-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag],
})
export class CdkDragDropOverviewExample {}
```

### app.css

```
.example-box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  position: relative;
  z-index: 1;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
```

## [Create a list of reorderable draggable elements](#create-a-list-of-reorderable-draggable-elements)

Add the `cdkDropList` directive to a parent element to group draggable elements into a reorderable collection. This defines where draggable elements can be dropped. The draggable elements in the drop list group rearrange automatically as an element moves.

The drag and drop directives don't update your data model. To update the data model, listen to the `cdkDropListDropped` event (once the user finishes dragging) and update the data model manually.

### app.html

```
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
  @for (movie of movies; track movie) {
    <div class="example-box" cdkDrag>{{ movie }}</div>
  }
</div>
```

### app.ts

```
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop sorting
 */
@Component({
  selector: 'cdk-drag-drop-sorting-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropSortingExample {
  movies = [
    'Episode I - The Phantom Menace',
    'Episode II - Attack of the Clones',
    'Episode III - Revenge of the Sith',
    'Episode IV - A New Hope',
    'Episode V - The Empire Strikes Back',
    'Episode VI - Return of the Jedi',
    'Episode VII - The Force Awakens',
    'Episode VIII - The Last Jedi',
    'Episode IX - The Rise of Skywalker',
  ];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
  }
}
```

### app.css

```
.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  border: none;
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

You can use the [`CDK_DROP_LIST`](/api/cdk/drag-drop/CDK_DROP_LIST) injection token that can be used to reference instances of `cdkDropList`. For more information see the [dependency injection guide](/guide/di) and the [drop list injection token API](api/cdk/drag-drop/CDK_DROP_LIST).

## [Transfer draggable elements between lists](#transfer-draggable-elements-between-lists)

The `cdkDropList` directive supports transferring draggable elements between connected drop lists. There are two ways to connect one or more `cdkDropList` instances together:

* Set the `cdkDropListConnectedTo` property to another drop list.
* Wrap the elements in an element with the `cdkDropListGroup` attribute.

The `cdkDropListConnectedTo` directive works both with a direct reference to another `cdkDropList` or by referencing the id of another drop container.

```
<!-- This is valid -->
<div cdkDropList #listOne="cdkDropList" [cdkDropListConnectedTo]="[listTwo]"></div>
<div cdkDropList #listTwo="cdkDropList" [cdkDropListConnectedTo]="[listOne]"></div>

<!-- This is valid as well -->
<div cdkDropList id="list-one" [cdkDropListConnectedTo]="['list-two']"></div>
<div cdkDropList id="list-two" [cdkDropListConnectedTo]="['list-one']"></div>
```

### app.html

```
<div class="example-container">
  <h2>To do</h2>

  <div
    cdkDropList
    #todoList="cdkDropList"
    [cdkDropListData]="todo"
    [cdkDropListConnectedTo]="[doneList]"
    class="example-list"
    (cdkDropListDropped)="drop($event)"
  >
    @for (item of todo; track item) {
      <div class="example-box" cdkDrag>{{ item }}</div>
    }
  </div>
</div>

<div class="example-container">
  <h2>Done</h2>

  <div
    cdkDropList
    #doneList="cdkDropList"
    [cdkDropListData]="done"
    [cdkDropListConnectedTo]="[todoList]"
    class="example-list"
    (cdkDropListDropped)="drop($event)"
  >
    @for (item of done; track item) {
      <div class="example-box" cdkDrag>{{ item }}</div>
    }
  </div>
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDropList,
  moveItemInArray,
  transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop connected sorting
 */
@Component({
  selector: 'cdk-drag-drop-connected-sorting-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropConnectedSortingExample {
  todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep'];

  done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog'];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
    }
  }
}
```

### app.css

```
.example-container {
  width: 400px;
  max-width: 100%;
  margin: 0 25px 25px 0;
  display: inline-block;
  vertical-align: top;
}

.example-list {
  border: solid 1px #ccc;
  min-height: 60px;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  display: block;
  font-family: sans-serif;
}

h2 {
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

Use the `cdkDropListGroup` directive if you have an unknown number of connected drop lists to set up the connection automatically. Any new `cdkDropList` that is added under a group automatically connects to all other lists.

```
<div cdkDropListGroup>
  <!-- All lists in here will be connected. -->
  @for (list of lists; track list) {
    <div cdkDropList></div>
  }
</div>
```

### app.html

```
<div cdkDropListGroup>
  <div class="example-container">
    <h2>To do</h2>

    <div
      cdkDropList
      [cdkDropListData]="todo"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
      @for (item of todo; track item) {
        <div class="example-box" cdkDrag>{{ item }}</div>
      }
    </div>
  </div>

  <div class="example-container">
    <h2>Done</h2>

    <div
      cdkDropList
      [cdkDropListData]="done"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
      @for (item of done; track item) {
        <div class="example-box" cdkDrag>{{ item }}</div>
      }
    </div>
  </div>
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDropList,
  CdkDropListGroup,
  moveItemInArray,
  transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop connected sorting group
 */
@Component({
  selector: 'cdk-drag-drop-connected-sorting-group-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropListGroup, CdkDropList, CdkDrag],
})
export class CdkDragDropConnectedSortingGroupExample {
  todo = ['Get to work', 'Pick up groceries', 'Go home', 'Fall asleep'];

  done = ['Get up', 'Brush teeth', 'Take a shower', 'Check e-mail', 'Walk dog'];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
    }
  }
}
```

### app.css

```
.example-container {
  width: 400px;
  max-width: 100%;
  margin: 0 25px 25px 0;
  display: inline-block;
  vertical-align: top;
}

.example-list {
  border: solid 1px #ccc;
  min-height: 60px;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  display: block;
  font-family: sans-serif;
}

h2 {
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

You can use the [`CDK_DROP_LIST_GROUP`](/api/cdk/drag-drop/CDK_DROP_LIST_GROUP) injection token that can be used to reference instances of `cdkDropListGroup`. For more information see the [dependency injection guide](/guide/di) and the [drop list group injection token API](api/cdk/drag-drop/CDK_DROP_LIST_GROUP).

### [Selective dragging](#selective-dragging)

By default, a user can move `cdkDrag` elements from one container into another connected container. For more fine-grained control over which elements can be dropped into a container, use `cdkDropListEnterPredicate`. Angular calls the predicate whenever a draggable element enters a new container. Depending on whether the predicate returns true or false, the item may or may not be allowed into the new container.

### app.html

```
<div class="example-container">
  <h2>Available numbers</h2>

  <div
    id="all"
    cdkDropList
    [cdkDropListData]="all"
    cdkDropListConnectedTo="even"
    class="example-list"
    (cdkDropListDropped)="drop($event)"
    [cdkDropListEnterPredicate]="noReturnPredicate"
  >
    @for (number of all; track number) {
      <div class="example-box" [cdkDragData]="number" cdkDrag>{{ number }}</div>
    }
  </div>
</div>

<div class="example-container">
  <h2>Even numbers</h2>

  <div
    id="even"
    cdkDropList
    [cdkDropListData]="even"
    cdkDropListConnectedTo="all"
    class="example-list"
    (cdkDropListDropped)="drop($event)"
    [cdkDropListEnterPredicate]="evenPredicate"
  >
    @for (number of even; track number) {
      <div class="example-box" cdkDrag [cdkDragData]="number">{{ number }}</div>
    }
  </div>
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDropList,
  moveItemInArray,
  transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop enter predicate
 */
@Component({
  selector: 'cdk-drag-drop-enter-predicate-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropEnterPredicateExample {
  all = [1, 2, 3, 4, 5, 6, 7, 8, 9];
  even = [10];

  drop(event: CdkDragDrop<number[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
    }
  }

  /** Predicate function that only allows even numbers to be dropped into a list. */
  evenPredicate(item: CdkDrag<number>) {
    return item.data % 2 === 0;
  }

  /** Predicate function that doesn't allow items to be dropped into a list. */
  noReturnPredicate() {
    return false;
  }
}
```

### app.css

```
.example-container {
  width: 400px;
  max-width: 100%;
  margin: 0 25px 25px 0;
  display: inline-block;
  vertical-align: top;
}

.example-list {
  border: solid 1px #ccc;
  min-height: 60px;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  display: block;
}

h2 {
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

## [Attach data](#attach-data)

You can associate some arbitrary data with both `cdkDrag` and `cdkDropList` by setting `cdkDragData` or `cdkDropListData`, respectively. You can bind to the events fired from both directives that will include this data, allowing you to easily identify the origin of the drag or drop interaction.

```
@for (list of lists; track list) {
  <div cdkDropList [cdkDropListData]="list" (cdkDropListDropped)="drop($event)">
    @for (item of list; track item) {
      <div cdkDrag [cdkDragData]="item"></div>
    }
  </div>
}
```

## [Dragging customizations](#dragging-customizations)

### [Customize drag handle](#customize-drag-handle)

By default, the user can drag the entire `cdkDrag` element to move it around. To restrict the user to only be able to do so using a handle element, add the `cdkDragHandle` directive to an element inside of `cdkDrag`. You can have as many `cdkDragHandle` elements as you want.

### app.html

```
<div class="example-box" cdkDrag>
  I can only be dragged using the handle

  <div class="example-handle" cdkDragHandle>
    <svg width="24px" fill="currentColor" viewBox="0 0 24 24">
      <path
        d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"
      ></path>
      <path d="M0 0h24v24H0z" fill="none"></path>
    </svg>
  </div>
</div>
```

### app.ts

```
import {CdkDrag, CdkDragHandle} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop with a handle
 */
@Component({
  selector: 'cdk-drag-drop-handle-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag, CdkDragHandle],
})
export class CdkDragDropHandleExample {}
```

### app.css

```
.example-box {
  width: 200px;
  height: 200px;
  padding: 10px;
  box-sizing: border-box;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  position: relative;
  z-index: 1;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.example-handle {
  position: absolute;
  top: 10px;
  right: 10px;
  color: #ccc;
  cursor: move;
  width: 24px;
  height: 24px;
}
```

You can use the [`CDK_DRAG_HANDLE`](/api/cdk/drag-drop/CDK_DRAG_HANDLE) injection token that can be used to reference instances of `cdkDragHandle`. For more information see the [dependency injection guide](/guide/di) and the [drag handle injection token API](api/cdk/drag-drop/CDK_DRAG_HANDLE).

### [Customize drag preview](#customize-drag-preview)

A preview element becomes visible when a `cdkDrag` element is being dragged. By default, the preview is a clone of the original element positioned next to the user's cursor.

To customize the preview, provide a custom template via `*cdkDragPreview`. The custom preview won't match the size of the original dragged element since assumptions aren't made about the element's content. To match the size of the element for the drag preview, pass true to the `matchSize` input.

The cloned element removes its id attribute in order to avoid having multiple elements with the same id on the page. This will cause any CSS that targets that id not to be applied.

### app.html

```
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
  @for (movie of movies; track movie) {
    <div class="example-box" cdkDrag>
      {{ movie.title }}
      <img *cdkDragPreview [src]="movie.poster" [alt]="movie.title" />
    </div>
  }
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDragPreview,
  CdkDropList,
  moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop custom preview
 */
@Component({
  selector: 'cdk-drag-drop-custom-preview-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag, CdkDragPreview],
})
export class CdkDragDropCustomPreviewExample {
  // tslint:disable:max-line-length
  movies = [
    {
      title: 'Episode I - The Phantom Menace',
      poster: 'https://upload.wikimedia.org/wikipedia/en/4/40/Star_Wars_Phantom_Menace_poster.jpg',
    },
    {
      title: 'Episode II - Attack of the Clones',
      poster:
        'https://upload.wikimedia.org/wikipedia/en/3/32/Star_Wars_-_Episode_II_Attack_of_the_Clones_%28movie_poster%29.jpg',
    },
    {
      title: 'Episode III - Revenge of the Sith',
      poster:
        'https://upload.wikimedia.org/wikipedia/en/9/93/Star_Wars_Episode_III_Revenge_of_the_Sith_poster.jpg',
    },
    {
      title: 'Episode IV - A New Hope',
      poster: 'https://upload.wikimedia.org/wikipedia/en/8/87/StarWarsMoviePoster1977.jpg',
    },
    {
      title: 'Episode V - The Empire Strikes Back',
      poster:
        'https://upload.wikimedia.org/wikipedia/en/3/3f/The_Empire_Strikes_Back_%281980_film%29.jpg',
    },
    {
      title: 'Episode VI - Return of the Jedi',
      poster: 'https://upload.wikimedia.org/wikipedia/en/b/b2/ReturnOfTheJediPoster1983.jpg',
    },
    {
      title: 'Episode VII - The Force Awakens',
      poster:
        'https://upload.wikimedia.org/wikipedia/en/a/a2/Star_Wars_The_Force_Awakens_Theatrical_Poster.jpg',
    },
    {
      title: 'Episode VIII - The Last Jedi',
      poster: 'https://upload.wikimedia.org/wikipedia/en/7/7f/Star_Wars_The_Last_Jedi.jpg',
    },
    {
      title: 'Episode IX – The Rise of Skywalker',
      poster:
        'https://upload.wikimedia.org/wikipedia/en/a/af/Star_Wars_The_Rise_of_Skywalker_poster.jpg',
    },
  ];
  // tslint:enable:max-line-length

  drop(event: CdkDragDrop<{title: string; poster: string}[]>) {
    moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
  }
}
```

### app.css

```
.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

You can use the [`CDK_DRAG_PREVIEW`](/api/cdk/drag-drop/CDK_DRAG_PREVIEW) injection token that can be used to reference instances of `cdkDragPreview`. For more information see the [dependency injection guide](/guide/di) and the [drag preview injection token API](api/cdk/drag-drop/CDK_DRAG_PREVIEW).

### [Customize drag insertion point](#customize-drag-insertion-point)

By default, Angular inserts the `cdkDrag` preview into the `<body>` of the page in order to avoid issues with positioning and overflow. This may not be desirable in some cases because the preview won't have its inherited styles applied.

You can change where Angular inserts the preview using the `cdkDragPreviewContainer` input on `cdkDrag`. The possible values are:

| Value                                                 | Description                                                                         | Advantages                                                                                                                  | Disadvantages                                                                                                                                                             |
| :---------------------------------------------------- | :---------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `global`                                              | Default value. Angular inserts the preview into the or the closest shadow root.     | Preview won't be affected by `z-index` or `overflow: hidden`. It also won't affect `:nth-child` selectors and flex layouts. | Doesn't retain inherited styles.                                                                                                                                          |
| `parent`                                              | Angular inserts the preview inside the parent of the element that is being dragged. | Preview inherits the same styles as the dragged element.                                                                    | Preview may be clipped by `overflow: hidden` or be placed under other elements due to `z-index`. Furthermore, it can affect `:nth-child` selectors and some flex layouts. |
| [`ElementRef`](/api/core/ElementRef) or `HTMLElement` | Angular inserts the preview into the specified element.                             | Preview inherits styles from the specified container element.                                                               | Preview may be clipped by `overflow: hidden` or be placed under other elements due to `z-index`. Furthermore, it can affect `:nth-child` selectors and some flex layouts. |

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update `previewContainer` within the config if the value is `global` or `parent`. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [Customize drag placeholder](#customize-drag-placeholder)

While a `cdkDrag` element is being dragged, the directive creates a placeholder element that shows where the element will be placed when dropped. By default, the placeholder is a clone of the element that is being dragged. You can replace the placeholder with a custom one using the `*cdkDragPlaceholder` directive:

### app.html

```
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
  @for (movie of movies; track movie) {
    <div class="example-box" cdkDrag>
      <div class="example-custom-placeholder" *cdkDragPlaceholder></div>
      {{ movie }}
    </div>
  }
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDragPlaceholder,
  CdkDropList,
  moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop custom placeholder
 */
@Component({
  selector: 'cdk-drag-drop-custom-placeholder-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag, CdkDragPlaceholder],
})
export class CdkDragDropCustomPlaceholderExample {
  movies = [
    'Episode I - The Phantom Menace',
    'Episode II - Attack of the Clones',
    'Episode III - Revenge of the Sith',
    'Episode IV - A New Hope',
    'Episode V - The Empire Strikes Back',
    'Episode VI - Return of the Jedi',
    'Episode VII - The Force Awakens',
    'Episode VIII - The Last Jedi',
    'Episode IX - The Rise of Skywalker',
  ];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.movies, event.previousIndex, event.currentIndex);
  }
}
```

### app.css

```
.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-custom-placeholder {
  background: #ccc;
  border: dotted 3px #999;
  min-height: 60px;
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

You can use the [`CDK_DRAG_PLACEHOLDER`](/api/cdk/drag-drop/CDK_DRAG_PLACEHOLDER) injection token that can be used to reference instances of [`cdkDragPlaceholder`](/api/cdk/drag-drop/CdkDragPlaceholder). For more information see the [dependency injection guide](/guide/di) and the [drag placeholder injection token API](api/cdk/drag-drop/CDK_DRAG_PLACEHOLDER).

### [Customize drag root element](#customize-drag-root-element)

Set the `cdkDragRootElement` attribute if there's an element that you want to make draggable but you don't have direct access to it.

The attribute accepts a selector and looks up the DOM until it finds an element that matches the selector. If an element is found, it becomes draggable. This is useful for cases such as making a dialog draggable.

### app.html

```
<button type="button" (click)="openDialog()">Open a draggable dialog</button>

<ng-template>
  <div class="example-dialog-content" cdkDrag cdkDragRootElement=".cdk-overlay-pane">
    Drag the dialog around!
  </div>
</ng-template>
```

### app.ts

```
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Overlay, OverlayRef} from '@angular/cdk/overlay';
import {TemplatePortal} from '@angular/cdk/portal';
import {
  AfterViewInit,
  Component,
  OnDestroy,
  TemplateRef,
  ViewContainerRef,
  inject,
  viewChild,
} from '@angular/core';

/**
 * @title Drag&Drop with alternate root element
 */
@Component({
  selector: 'cdk-drag-drop-root-element-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag],
})
export class CdkDragDropRootElementExample implements AfterViewInit, OnDestroy {
  private _overlay = inject(Overlay);
  private _viewContainerRef = inject(ViewContainerRef);
  private _dialogTemplate = viewChild.required(TemplateRef);
  private _overlayRef!: OverlayRef;
  private _portal!: TemplatePortal;

  ngAfterViewInit() {
    this._portal = new TemplatePortal(this._dialogTemplate(), this._viewContainerRef);
    this._overlayRef = this._overlay.create({
      positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
      hasBackdrop: true,
    });
    this._overlayRef.backdropClick().subscribe(() => this._overlayRef.detach());
  }

  ngOnDestroy() {
    this._overlayRef.dispose();
  }

  openDialog() {
    this._overlayRef.attach(this._portal);
  }
}
```

### app.css

```
.example-dialog-content {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #fff;
  border-radius: 4px;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-dialog-content:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update `rootElementSelector` within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [Set DOM position of a draggable element](#set-dom-position-of-a-draggable-element)

By default, `cdkDrag` elements not in a `cdkDropList` move from their normal DOM position only when a user manually moves the element. Use the `cdkDragFreeDragPosition` input to explicitly set the element’s position. A common use case for this is restoring a draggable element's position after a user has navigated away and then returned.

### app.html

```
<p>
  <button type="button" (click)="changePosition()">Change element position</button>
</p>

<div class="example-box" cdkDrag [cdkDragFreeDragPosition]="dragPosition">Drag me around</div>
```

### app.ts

```
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Programmatically setting the free drag position
 */
@Component({
  selector: 'cdk-drag-drop-free-drag-position-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag],
})
export class CdkDragDropFreeDragPositionExample {
  dragPosition = {x: 0, y: 0};

  changePosition() {
    this.dragPosition = {x: this.dragPosition.x + 50, y: this.dragPosition.y + 50};
  }
}
```

### app.css

```
.example-box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  position: relative;
  z-index: 1;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
```

### [Restrict movement within an element](#restrict-movement-within-an-element)

To stop the user from being able to drag a `cdkDrag` element outside of another element, pass a CSS selector to the `cdkDragBoundary` attribute. This attribute accepts a selector and looks up the DOM until it finds an element that matches it. If a match is found, the element becomes the boundary that the draggable element can't be dragged outside of `cdkDragBoundary` can also be used when `cdkDrag` is placed inside a `cdkDropList`.

### app.html

```
<div class="example-boundary">
  <div class="example-box" cdkDragBoundary=".example-boundary" cdkDrag>
    I can only be dragged within the dotted container
  </div>
</div>
```

### app.ts

```
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop boundary
 */
@Component({
  selector: 'cdk-drag-drop-boundary-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag],
})
export class CdkDragDropBoundaryExample {}
```

### app.css

```
.example-box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  margin-right: 25px;
  position: relative;
  z-index: 1;
  box-sizing: border-box;
  padding: 10px;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.example-boundary {
  width: 400px;
  height: 400px;
  max-width: 100%;
  border: dotted #ccc 2px;
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update boundaryElement within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [Restrict movement along an axis](#restrict-movement-along-an-axis)

By default, `cdkDrag` allows free movement in all directions. To restrict dragging to a specific axis, set `cdkDragLockAxis` to either "x" or "y" on `cdkDrag`. To restrict dragging for multiple draggable elements within `cdkDropList`, set `cdkDropListLockAxis` on `cdkDropList` instead.

### app.html

```
<div class="example-box" cdkDragLockAxis="y" cdkDrag>I can only be dragged up/down</div>

<div class="example-box" cdkDragLockAxis="x" cdkDrag>I can only be dragged left/right</div>
```

### app.ts

```
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop position locking
 */
@Component({
  selector: 'cdk-drag-drop-axis-lock-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag],
})
export class CdkDragDropAxisLockExample {}
```

### app.css

```
.example-box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  margin-right: 25px;
  position: relative;
  z-index: 1;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update `lockAxis` within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [Delay dragging](#delay-dragging)

By default when the user puts their pointer down on a `cdkDrag`, the dragging sequence starts. This behavior might not be desirable in cases like fullscreen draggable elements on touch devices where the user might accidentally trigger a drag event as they scroll on the page.

You can delay the dragging sequence using the `cdkDragStartDelay` input. The input waits for the user to hold down their pointer for the specified number of milliseconds before dragging the element.

### app.html

```
<div class="example-box" cdkDrag [cdkDragStartDelay]="1000">Dragging starts after one second</div>
```

### app.ts

```
import {CdkDrag} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Delay dragging
 */
@Component({
  selector: 'cdk-drag-drop-delay-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDrag],
})
export class CdkDragDropDelayExample {}
```

### app.css

```
.example-box {
  width: 200px;
  height: 200px;
  border: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  cursor: move;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  background: #fff;
  border-radius: 4px;
  position: relative;
  z-index: 1;
  font-family: sans-serif;
  transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
  box-shadow:
    0 3px 1px -2px rgba(0, 0, 0, 0.2),
    0 2px 2px 0 rgba(0, 0, 0, 0.14),
    0 1px 5px 0 rgba(0, 0, 0, 0.12);
}

.example-box:active {
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update dragStartDelay within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [Disable dragging](#disable-dragging)

If you want to disable dragging for a particular drag item, set the `cdkDragDisabled` input on a `cdkDrag` item to true or false. You can disable an entire list using the `cdkDropListDisabled` input on a `cdkDropList`. It is also possible to disable a specific handle via `cdkDragHandleDisabled` on `cdkDragHandle`.

### app.html

```
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
  @for (item of items; track item) {
    <div class="example-box" cdkDrag [cdkDragDisabled]="item.disabled">{{ item.value }}</div>
  }
</div>
```

### app.ts

```
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop disabled
 */
@Component({
  selector: 'cdk-drag-drop-disabled-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropDisabledExample {
  items = [
    {value: 'I can be dragged', disabled: false},
    {value: 'I cannot be dragged', disabled: true},
    {value: 'I can also be dragged', disabled: false},
  ];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.items, event.previousIndex, event.currentIndex);
  }
}
```

### app.css

```
.example-list {
  width: 500px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: block;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.example-box.cdk-drag-disabled {
  background: #ccc;
  cursor: not-allowed;
  user-select: none;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update `draggingDisabled` within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

## [Sorting customizations](#sorting-customizations)

### [List orientation](#list-orientation)

By default, the `cdkDropList` directive assumes lists are vertical. This can be changed by setting the `cdkDropListOrientation` property to horizontal.

### app.html

```
<div
  cdkDropList
  cdkDropListOrientation="horizontal"
  class="example-list"
  (cdkDropListDropped)="drop($event)"
>
  @for (timePeriod of timePeriods; track timePeriod) {
    <div class="example-box" cdkDrag>{{ timePeriod }}</div>
  }
</div>
```

### app.ts

```
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop horizontal sorting
 */
@Component({
  selector: 'cdk-drag-drop-horizontal-sorting-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropHorizontalSortingExample {
  timePeriods = [
    'Bronze age',
    'Iron age',
    'Middle ages',
    'Early modern period',
    'Long nineteenth century',
  ];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.timePeriods, event.previousIndex, event.currentIndex);
  }
}
```

### app.css

```
.example-list {
  width: 1000px;
  max-width: 100%;
  border: solid 1px #ccc;
  min-height: 60px;
  display: flex;
  flex-direction: row;
  background: white;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border-right: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  flex-grow: 1;
  flex-basis: 0;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update `listOrientation` within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [List wrapping](#list-wrapping)

By default, the `cdkDropList` sorts the draggable elements by moving them around using a CSS transform. This allows for the sorting to be animated which provides a better user experience. However this also comes with the drawback that the drop list works only in one direction: vertically or horizontally.

If you have a sortable list that needs to wrap onto new lines, you can set `cdkDropListOrientation` attribute to `mixed`. This causes the list to use a different strategy of sorting the elements which involves moving them in the DOM. However, the list can no longer animate the sorting action.

### app.html

```
<div
  cdkDropList
  cdkDropListOrientation="mixed"
  class="example-list"
  (cdkDropListDropped)="drop($event)"
>
  @for (item of items; track item) {
    <div class="example-box" cdkDrag>{{ item }}</div>
  }
</div>
```

### app.ts

```
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop horizontal wrapping list
 */
@Component({
  selector: 'cdk-drag-drop-mixed-sorting-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropMixedSortingExample {
  items = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];

  drop(event: CdkDragDrop<string[]>) {
    moveItemInArray(this.items, event.previousIndex, event.currentIndex);
  }
}
```

### app.css

```
.example-list {
  display: flex;
  flex-wrap: wrap;
  width: 505px;
  max-width: 100%;
  gap: 15px;
  padding: 15px;
  border: solid 1px #ccc;
  min-height: 60px;
  border-radius: 4px;
  overflow: hidden;
}

.example-box {
  padding: 20px 10px;
  border: solid 1px #ccc;
  border-radius: 4px;
  color: rgba(0, 0, 0, 0.87);
  display: inline-block;
  box-sizing: border-box;
  cursor: move;
  background: white;
  text-align: center;
  font-size: 14px;
  min-width: 115px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

### [Selective sorting](#selective-sorting)

By default, `cdkDrag` elements are sorted into any position inside of a `cdkDropList`. To change this behavior, set the `cdkDropListSortPredicate` attribute which takes in a function. The predicate function is called whenever a draggable element is about to be moved into a new index within the drop list. If the predicate returns true, the item will be moved into the new index, otherwise it will keep its current position.

### app.html

```
<div
  cdkDropList
  class="example-list"
  (cdkDropListDropped)="drop($event)"
  [cdkDropListSortPredicate]="sortPredicate"
>
  @for (number of numbers; track number) {
    <div class="example-box" [cdkDragData]="number" cdkDrag>{{ number }}</div>
  }
</div>
```

### app.ts

```
import {CdkDrag, CdkDragDrop, CdkDropList, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop sort predicate
 */
@Component({
  selector: 'cdk-drag-drop-sort-predicate-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropSortPredicateExample {
  numbers = [1, 2, 3, 4, 5, 6, 7, 8];

  drop(event: CdkDragDrop<unknown>) {
    moveItemInArray(this.numbers, event.previousIndex, event.currentIndex);
  }

  /**
   * Predicate function that only allows even numbers to be
   * sorted into even indices and odd numbers at odd indices.
   */
  sortPredicate(index: number, item: CdkDrag<number>) {
    return (index + 1) % 2 === item.data % 2;
  }
}
```

### app.css

```
.example-list {
  border: solid 1px #ccc;
  min-height: 60px;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  display: block;
  width: 400px;
  max-width: 100%;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

### [Disable sorting](#disable-sorting)

There are cases where draggable elements can be dragged out of one `cdkDropList` into another, however the user shouldn't be able to sort them within the source list. For these cases, add the `cdkDropListSortingDisabled` attribute to prevent the draggable elements in a `cdkDropList` from sorting. This preserves the dragged element's initial position in the source list if it does not get dragged to a new valid position.

### app.html

```
<div cdkDropListGroup>
  <div class="example-container">
    <h2>Available items</h2>

    <div
      cdkDropList
      [cdkDropListData]="items"
      class="example-list"
      cdkDropListSortingDisabled
      (cdkDropListDropped)="drop($event)"
    >
      @for (item of items; track item) {
        <div class="example-box" cdkDrag>{{ item }}</div>
      }
    </div>
  </div>

  <div class="example-container">
    <h2>Shopping basket</h2>

    <div
      cdkDropList
      [cdkDropListData]="basket"
      class="example-list"
      (cdkDropListDropped)="drop($event)"
    >
      @for (item of basket; track item) {
        <div class="example-box" cdkDrag>{{ item }}</div>
      }
    </div>
  </div>
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDropList,
  CdkDropListGroup,
  moveItemInArray,
  transferArrayItem,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop disabled sorting
 */
@Component({
  selector: 'cdk-drag-drop-disabled-sorting-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropListGroup, CdkDropList, CdkDrag],
})
export class CdkDragDropDisabledSortingExample {
  items = ['Carrots', 'Tomatoes', 'Onions', 'Apples', 'Avocados'];

  basket = ['Oranges', 'Bananas', 'Cucumbers'];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      transferArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
    }
  }
}
```

### app.css

```
.example-container {
  width: 400px;
  max-width: 100%;
  margin: 0 25px 25px 0;
  display: inline-block;
  vertical-align: top;
}

.example-list {
  border: solid 1px #ccc;
  min-height: 60px;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  display: block;
}

h2 {
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-placeholder {
  opacity: 0;
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

Alternatively, you can modify the [`CDK_DRAG_CONFIG`](/api/cdk/drag-drop/CDK_DRAG_CONFIG) injection token to update sortingDisabled within the config. For more information see the [dependency injection guide](/guide/di), [drag config injection token API](api/cdk/drag-drop/CDK_DRAG_CONFIG), and the [drag drop config API](api/cdk/drag-drop/DragDropConfig).

### [Copying items between lists](#copying-items-between-lists)

By default, when an item is dragged from one list to another, it is moved out of its original list. However, you can configure the directives to copy the item, leaving the original item in the source list.

To enable copying, you can set the `cdkDropListHasAnchor` input. This tells the `cdkDropList` to create an "anchor" element that stays in the original container and doesn't move with the item. If the user moves the item back into the original container, the anchor is removed automatically. The anchor element can be styled by targeting the `.cdk-drag-anchor` CSS class.

Combining `cdkDropListHasAnchor` with `cdkDropListSortingDisabled` makes it possible to construct a list from which a user can copy items without being able to reorder the source list (e.g. a product list and a shopping cart).

### app.html

```
<div class="example-container">
  <h2>Products</h2>

  <div
    cdkDropList
    [cdkDropListData]="products"
    [cdkDropListConnectedTo]="[cartList]"
    cdkDropListSortingDisabled
    cdkDropListHasAnchor
    class="example-list"
  >
    @for (product of products; track $index) {
      <div class="example-box" cdkDrag [cdkDragData]="product">{{ product }}</div>
    }
  </div>
</div>

<div class="example-container">
  <h2>Shopping cart</h2>

  <div
    cdkDropList
    #cartList="cdkDropList"
    [cdkDropListData]="cart"
    class="example-list"
    (cdkDropListDropped)="drop($event)"
  >
    @for (product of cart; track $index) {
      <div class="example-box" cdkDrag>{{ product }}</div>
    }
  </div>
</div>
```

### app.ts

```
import {
  CdkDrag,
  CdkDragDrop,
  CdkDropList,
  copyArrayItem,
  moveItemInArray,
} from '@angular/cdk/drag-drop';
import {Component} from '@angular/core';

/**
 * @title Drag&Drop copy between lists
 */
@Component({
  selector: 'cdk-drag-drop-copy-list-example',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CdkDropList, CdkDrag],
})
export class CdkDragDropCopyListExample {
  products = ['Bananas', 'Oranges', 'Bread', 'Butter', 'Soda', 'Eggs'];
  cart = ['Tomatoes'];

  drop(event: CdkDragDrop<string[]>) {
    if (event.previousContainer === event.container) {
      moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
    } else {
      copyArrayItem(
        event.previousContainer.data,
        event.container.data,
        event.previousIndex,
        event.currentIndex,
      );
    }
  }
}
```

### app.css

```
.example-container {
  width: 400px;
  max-width: 100%;
  margin: 0 25px 25px 0;
  display: inline-block;
  vertical-align: top;
  font-family: sans-serif;
}

.example-list {
  border: solid 1px #ccc;
  min-height: 60px;
  background: white;
  border-radius: 4px;
  overflow: hidden;
  display: block;
}

h2 {
  font-family: sans-serif;
}

.example-box {
  padding: 20px 10px;
  border-bottom: solid 1px #ccc;
  color: rgba(0, 0, 0, 0.87);
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  box-sizing: border-box;
  cursor: move;
  background: white;
  font-size: 14px;
  font-family: sans-serif;
}

.cdk-drag-preview {
  box-sizing: border-box;
  border-radius: 4px;
  box-shadow:
    0 5px 5px -3px rgba(0, 0, 0, 0.2),
    0 8px 10px 1px rgba(0, 0, 0, 0.14),
    0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.cdk-drag-animating {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}

.example-box:last-child {
  border: none;
}

.example-list.cdk-drop-list-dragging .example-box:not(.cdk-drag-placeholder) {
  transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
```

## [Customize animations](#customize-animations)

Drag and drop supports animations for both:

* Sorting a draggable element inside a list
* Moving the draggable element from the position that the user dropped it to the final position inside the list

To set up your animations, define a CSS transition that targets the transform property. The following classes can be used for animations:

| CSS class name      | Result of adding transition                                                                                                                                                                         |
| :------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| .cdk-drag           | Animate draggable elements as they are being sorted.                                                                                                                                                |
| .cdk-drag-animating | Animate the draggable element from its dropped position to the final position within the `cdkDropList`. This CSS class is applied to a `cdkDrag` element only when the dragging action has stopped. |

## [Styling](#styling)

Both `cdkDrag` and `cdkDropList` directives only apply essential styles needed for functionality. Applications can customize their styles by targeting these specified CSS classes.

| CSS class name           | Description                                                                                                                                                                                                                                                                                      |
| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| .cdk-drop-list           | Selector for the `cdkDropList` container elements.                                                                                                                                                                                                                                               |
| .cdk-drag                | Selector for `cdkDrag` elements.                                                                                                                                                                                                                                                                 |
| .cdk-drag-disabled       | Selector for disabled `cdkDrag` elements.                                                                                                                                                                                                                                                        |
| .cdk-drag-handle         | Selector for the host element of the `cdkDragHandle`.                                                                                                                                                                                                                                            |
| .cdk-drag-preview        | Selector for the drag preview element. This is the element that appears next to the cursor as a user drags an element in a sortable list. The element looks exactly like the element that is being dragged unless customized with a custom template through `*cdkDragPreview`.                   |
| .cdk-drag-placeholder    | Selector for the drag placeholder element. This is the element that is shown in the spot where the draggable element will be dragged to once the dragging action ends. This element looks exactly like the element that is being sorted unless customized with the cdkDragPlaceholder directive. |
| .cdk-drop-list-dragging  | Selector for `cdkDropList` container element that has a draggable element currently being dragged.                                                                                                                                                                                               |
| .cdk-drop-list-disabled  | Selector for `cdkDropList` container elements that are disabled.                                                                                                                                                                                                                                 |
| .cdk-drop-list-receiving | Selector for `cdkDropList` container element that has a draggable element it can receive from a connected drop list that is currently being dragged.                                                                                                                                             |
| .cdk-drag-anchor         | Selector for the anchor element that is created when `cdkDropListHasAnchor` is enabled. This element indicates the position from which the dragged item started.                                                                                                                                 |

## [Dragging in a scrollable container](#dragging-in-a-scrollable-container)

If your draggable items are inside a scrollable container (e.g., a `div` with `overflow: auto`), automatic scrolling will not work unless the scrollable container has the `cdkScrollable` directive. Without it, the CDK cannot detect or control the scroll behavior of the container during drag operations.

## [Integrations with other components](#integrations-with-other-components)

The CDK's drag-and-drop functionality can be integrated with different components. Common use cases include sortable `MatTable` components and sortable `MatTabGroup` components.

----
url: https://angular.dev/api/router/DefaultUrlSerializer
----

```
    
      class DefaultUrlSerializer implements UrlSerializer {}
    
    
```

Parses a url into a [`UrlTree`](/api/router/UrlTree)

@paramurl`string`

@returns`UrlTree`

Converts a [`UrlTree`](/api/router/UrlTree) into a url

@paramtree`UrlTree`

@returns`string`

## [Description](#description)

A default implementation of the [`UrlSerializer`](/api/router/UrlSerializer).

Example URLs:

```
/inbox/33(popup:compose)
/inbox/33;open=true/messages/44
```

DefaultUrlSerializer uses parentheses to serialize secondary segments (e.g., popup:compose), the colon syntax to specify the outlet, and the ';parameter=value' syntax (e.g., open=true) to specify route specific parameters.

Jump to details

----
url: https://angular.dev/api/common/http/HttpResponse
----

```
    
      class HttpResponse<T> extends HttpResponseBase {
}
    
    
```

Construct a new [`HttpResponse`](/api/common/http/HttpResponse).

@paraminit`{ body?: T | null | undefined; headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; redirected?: boolean | undefined; responseType?: ResponseType | undefined; }`

@returns`HttpResponse<T>`

The response body, or `null` if one was not returned.

@returns`HttpResponse<T>`

@paramupdate`{ headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; redirected?: boolean | undefined; responseType?: ResponseType | undefined; }`

@returns`HttpResponse<T>`

@paramupdate`{ body?: V | null | undefined; headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; redirected?: boolean | undefined; responseType?: ResponseType | undefined; }`

@returns`HttpResponse<V>`

A full HTTP response, including a typed response body (which may be `null` if one was not returned).

[`HttpResponse`](/api/common/http/HttpResponse) is a [`HttpEvent`](/api/common/http/HttpEvent) available on the response event stream.

Jump to details

----
url: https://angular.dev/api/platform-browser/BootstrapContext
----

```
    
      interface BootstrapContext {}
    
    
```

A reference to a platform.

Jump to details

----
url: https://angular.dev/api/platform-server/PlatformState
----

```
    
      class PlatformState {}
    
    
```

@param\_doc`any`

@returns`PlatformState`

Renders the current state of the platform to string.

@returns`string`

Returns the current DOM state.

@returns`any`

Jump to details

----
url: https://angular.dev/guide/templates/ng-content
----

`<ng-content>` is a special element that accepts markup or a template fragment and controls how components render content. It does not render a real DOM element.

Here is an example of a `BaseButton` component that accepts any markup from its parent.

### base-button/base-button.ts

```
import {Component} from '@angular/core';

@Component({
  selector: 'button[baseButton]',
  template: `<ng-content />`,
})
export class BaseButton {}
```

### app.ts

```
import {Component} from '@angular/core';
import {BaseButton} from './base-button';

@Component({
  selector: 'app-root',
  imports: [BaseButton],
  template: `<button baseButton>Next <span class="icon arrow-right"></span></button>`,
})
export class App {}
```

For more detail, check out the [`<ng-content>` in-depth guide](/guide/components/content-projection) for other ways you can leverage this pattern.

----
url: https://angular.dev/api/core/ErrorHandler
----

```
    
      class ErrorHandler {}
    
    
```

@paramerror`any`

@returns`void`

Callback to handle errors happening during the rendering of a component/directive view.

@paramerror`Error`

The error that was thrown.

@paramdetails`ErrorDetails`

Additional information about the error.

@returns`void`

## [Description](#description)

Provides a hook for centralized exception handling.

The default implementation of [`ErrorHandler`](/api/core/ErrorHandler) prints error messages to the `console`. To intercept error handling, write a custom exception handler that replaces this default as appropriate for your app.

## [Usage Notes](#usage-notes)

### [Example](#example)

```
class MyErrorHandler implements ErrorHandler {
  handleError(error) {
    // do something with the exception
  }
}

// Provide in standalone apps
bootstrapApplication(AppComponent, {
  providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})

// Provide in module-based apps
@NgModule({
  providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
})
class MyModule {}
```

Jump to details

----
url: https://angular.dev/api/common/DatePipe
----

## [Pipe usage](/api/common/DatePipe#pipe-usage)

```
{{ value_expression | date:format:timezone:locale }}
```

## [API](/api/common/DatePipe#api)

```
    
      class DatePipe implements PipeTransform {
}
    
    
```

@paramlocale`string`

@paramdefaultTimezone`string | null | undefined`

@paramdefaultOptions`DatePipeConfig | null | undefined`

@returns`DatePipe`

@paramvalue`string | number | Date`

The date expression: a `Date` object, a number (milliseconds since UTC epoch), or an ISO string (<https://www.w3.org/TR/NOTE-datetime>).

@paramformat`string | undefined`

The date/time components to include, using predefined options or a custom format string. When not provided, the [`DatePipe`](/api/common/DatePipe) looks for the value using the [`DATE_PIPE_DEFAULT_OPTIONS`](/api/common/DATE_PIPE_DEFAULT_OPTIONS) injection token (and reads the `dateFormat` property). If the token is not configured, the `mediumDate` is used as a value.

@paramtimezone`string | undefined`

A timezone offset (such as `'+0430'`). When not provided, the [`DatePipe`](/api/common/DatePipe) looks for the value using the [`DATE_PIPE_DEFAULT_OPTIONS`](/api/common/DATE_PIPE_DEFAULT_OPTIONS) injection token (and reads the `timezone` property). If the token is not configured, the end-user's local system timezone is used as a value.

@paramlocale`string | undefined`

A locale code for the locale format rules to use. When not supplied, uses the value of [`LOCALE_ID`](/api/core/LOCALE_ID), which is `en-US` by default. See [Setting your app locale](guide/i18n/locale-id).

@returns`string | null`

A date string in the desired format.

@paramvalue`null | undefined`

@paramformat`string | undefined`

@paramtimezone`string | undefined`

@paramlocale`string | undefined`

@returns`null`

@paramvalue`string | number | Date | null | undefined`

@paramformat`string | undefined`

@paramtimezone`string | undefined`

@paramlocale`string | undefined`

@returns`string | null`

## [Description](/api/common/DatePipe#description)

Formats a date value according to locale rules.

[`DatePipe`](/api/common/DatePipe) is executed only when it detects a pure change to the input value. A pure change is either a change to a primitive input value (such as `String`, `Number`, `Boolean`, or `Symbol`), or a changed object reference (such as `Date`, `Array`, `Function`, or `Object`).

Note that mutating a `Date` object does not cause the pipe to be rendered again. To ensure that the pipe is executed, you must create a new `Date` object.

Only the `en-US` locale data comes with Angular. To localize dates in another language, you must import the corresponding locale data. See the [I18n guide](guide/i18n/format-data-locale) for more information.

The time zone of the formatted value can be specified either by passing it in as the second parameter of the pipe, or by setting the default through the [`DATE_PIPE_DEFAULT_OPTIONS`](/api/common/DATE_PIPE_DEFAULT_OPTIONS) injection token. The value that is passed in as the second parameter takes precedence over the one defined using the injection token.

## [Exported by](/api/common/DatePipe#exported-by)

* `CommonModule`

## [Usage Notes](/api/common/DatePipe#usage-notes)

The result of this pipe is not reevaluated when the input is mutated. To avoid the need to reformat the date on every change-detection cycle, treat the date as an immutable object and change the reference when the pipe needs to run again.

### [Pre-defined format options](/api/common/DatePipe#pre-defined-format-options)

| Option         | Equivalent to                       | Examples (given in `en-US` locale)              |
| -------------- | ----------------------------------- | ----------------------------------------------- |
| `'short'`      | `'M/d/yy, h:mm a'`                  | `6/15/15, 9:03 AM`                              |
| `'medium'`     | `'MMM d, y, h:mm:ss a'`             | `Jun 15, 2015, 9:03:01 AM`                      |
| `'long'`       | `'MMMM d, y, h:mm:ss a z'`          | `June 15, 2015 at 9:03:01 AM GMT+1`             |
| `'full'`       | `'EEEE, MMMM d, y, h:mm:ss a zzzz'` | `Monday, June 15, 2015 at 9:03:01 AM GMT+01:00` |
| `'shortDate'`  | `'M/d/yy'`                          | `6/15/15`                                       |
| `'mediumDate'` | `'MMM d, y'`                        | `Jun 15, 2015`                                  |
| `'longDate'`   | `'MMMM d, y'`                       | `June 15, 2015`                                 |
| `'fullDate'`   | `'EEEE, MMMM d, y'`                 | `Monday, June 15, 2015`                         |
| `'shortTime'`  | `'h:mm a'`                          | `9:03 AM`                                       |
| `'mediumTime'` | `'h:mm:ss a'`                       | `9:03:01 AM`                                    |
| `'longTime'`   | `'h:mm:ss a z'`                     | `9:03:01 AM GMT+1`                              |
| `'fullTime'`   | `'h:mm:ss a zzzz'`                  | `9:03:01 AM GMT+01:00`                          |

### [Custom format options](/api/common/DatePipe#custom-format-options)

You can construct a format string using symbols to specify the components of a date-time value, as described in the following table. Format details depend on the locale. Fields marked with (\*) are only available in the extra data set for the given locale.

| Field type              | Format      | Description                                                  | Example Value                                              |
| ----------------------- | ----------- | ------------------------------------------------------------ | ---------------------------------------------------------- |
| Era                     | G, GG & GGG | Abbreviated                                                  | AD                                                         |
|                         | GGGG        | Wide                                                         | Anno Domini                                                |
|                         | GGGGG       | Narrow                                                       | A                                                          |
| Year                    | y           | Numeric: minimum digits                                      | 2, 20, 201, 2017, 20173                                    |
|                         | yy          | Numeric: 2 digits + zero padded                              | 02, 20, 01, 17, 73                                         |
|                         | yyy         | Numeric: 3 digits + zero padded                              | 002, 020, 201, 2017, 20173                                 |
|                         | yyyy        | Numeric: 4 digits or more + zero padded                      | 0002, 0020, 0201, 2017, 20173                              |
| ISO Week-numbering year | Y           | Numeric: minimum digits                                      | 2, 20, 201, 2017, 20173                                    |
|                         | YY          | Numeric: 2 digits + zero padded                              | 02, 20, 01, 17, 73                                         |
|                         | YYY         | Numeric: 3 digits + zero padded                              | 002, 020, 201, 2017, 20173                                 |
|                         | YYYY        | Numeric: 4 digits or more + zero padded                      | 0002, 0020, 0201, 2017, 20173                              |
| Month                   | M           | Numeric: 1 digit                                             | 9, 12                                                      |
|                         | MM          | Numeric: 2 digits + zero padded                              | 09, 12                                                     |
|                         | MMM         | Abbreviated                                                  | Sep                                                        |
|                         | MMMM        | Wide                                                         | September                                                  |
|                         | MMMMM       | Narrow                                                       | S                                                          |
| Month standalone        | L           | Numeric: 1 digit                                             | 9, 12                                                      |
|                         | LL          | Numeric: 2 digits + zero padded                              | 09, 12                                                     |
|                         | LLL         | Abbreviated                                                  | Sep                                                        |
|                         | LLLL        | Wide                                                         | September                                                  |
|                         | LLLLL       | Narrow                                                       | S                                                          |
| ISO Week of year        | w           | Numeric: minimum digits                                      | 1... 53                                                    |
|                         | ww          | Numeric: 2 digits + zero padded                              | 01... 53                                                   |
| Week of month           | W           | Numeric: 1 digit                                             | 1... 5                                                     |
| Day of month            | d           | Numeric: minimum digits                                      | 1                                                          |
|                         | dd          | Numeric: 2 digits + zero padded                              | 01                                                         |
| Week day                | E, EE & EEE | Abbreviated                                                  | Tue                                                        |
|                         | EEEE        | Wide                                                         | Tuesday                                                    |
|                         | EEEEE       | Narrow                                                       | T                                                          |
|                         | EEEEEE      | Short                                                        | Tu                                                         |
| Week day standalone     | c, cc       | Numeric: 1 digit                                             | 2                                                          |
|                         | ccc         | Abbreviated                                                  | Tue                                                        |
|                         | cccc        | Wide                                                         | Tuesday                                                    |
|                         | ccccc       | Narrow                                                       | T                                                          |
|                         | cccccc      | Short                                                        | Tu                                                         |
| Period                  | a, aa & aaa | Abbreviated                                                  | am/pm or AM/PM                                             |
|                         | aaaa        | Wide (fallback to `a` when missing)                          | ante meridiem/post meridiem                                |
|                         | aaaaa       | Narrow                                                       | a/p                                                        |
| Period\*                | B, BB & BBB | Abbreviated                                                  | mid.                                                       |
|                         | BBBB        | Wide                                                         | am, pm, midnight, noon, morning, afternoon, evening, night |
|                         | BBBBB       | Narrow                                                       | md                                                         |
| Period standalone\*     | b, bb & bbb | Abbreviated                                                  | mid.                                                       |
|                         | bbbb        | Wide                                                         | am, pm, midnight, noon, morning, afternoon, evening, night |
|                         | bbbbb       | Narrow                                                       | md                                                         |
| Hour 1-12               | h           | Numeric: minimum digits                                      | 1, 12                                                      |
|                         | hh          | Numeric: 2 digits + zero padded                              | 01, 12                                                     |
| Hour 0-23               | H           | Numeric: minimum digits                                      | 0, 23                                                      |
|                         | HH          | Numeric: 2 digits + zero padded                              | 00, 23                                                     |
| Minute                  | m           | Numeric: minimum digits                                      | 8, 59                                                      |
|                         | mm          | Numeric: 2 digits + zero padded                              | 08, 59                                                     |
| Second                  | s           | Numeric: minimum digits                                      | 0... 59                                                    |
|                         | ss          | Numeric: 2 digits + zero padded                              | 00... 59                                                   |
| Fractional seconds      | S           | Numeric: 1 digit                                             | 0... 9                                                     |
|                         | SS          | Numeric: 2 digits + zero padded                              | 00... 99                                                   |
|                         | SSS         | Numeric: 3 digits + zero padded (= milliseconds)             | 000... 999                                                 |
| Zone                    | z, zz & zzz | Short specific non location format (fallback to O)           | GMT-8                                                      |
|                         | zzzz        | Long specific non location format (fallback to OOOO)         | GMT-08:00                                                  |
|                         | Z, ZZ & ZZZ | ISO8601 basic format                                         | -0800                                                      |
|                         | ZZZZ        | Long localized GMT format                                    | GMT-8:00                                                   |
|                         | ZZZZZ       | ISO8601 extended format + Z indicator for offset 0 (= XXXXX) | -08:00                                                     |
|                         | O, OO & OOO | Short localized GMT format                                   | GMT-8                                                      |
|                         | OOOO        | Long localized GMT format                                    | GMT-08:00                                                  |

### [Format examples](/api/common/DatePipe#format-examples)

These examples transform a date into various formats, assuming that `dateObj` is a JavaScript `Date` object for year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11, given in the local time for the `en-US` locale.

```
{{ dateObj | date }}               // output is 'Jun 15, 2015'
{{ dateObj | date:'medium' }}      // output is 'Jun 15, 2015, 9:43:11 PM'
{{ dateObj | date:'shortTime' }}   // output is '9:43 PM'
{{ dateObj | date:'mm:ss' }}       // output is '43:11'
{{ dateObj | date:"MMM dd, yyyy 'at' hh:mm a" }}  // output is 'Jun 15, 2015 at 09:43 PM'
```

### [Usage example](/api/common/DatePipe#usage-example)

The following component uses a date pipe to display the current date in different formats.

```
@Component({
 selector: 'date-pipe',
 template: `<div>
   <p>Today is {{today | date}}</p>
   <p>Or if you prefer, {{today | date:'fullDate'}}</p>
   <p>The time is {{today | date:'h:mm a z'}}</p>
 </div>`
})
// Get the current date and time as a date-time value.
export class DatePipeComponent {
  today: number = Date.now();
}
```

Jump to details

----
url: https://angular.dev/api/common/TitleCasePipe
----

```
{{ value_expression | titlecase }}
```

## [API](#api)

```
    
      class TitleCasePipe implements PipeTransform {
}
    
    
```

@paramvalue`string`

The string to transform to title case.

@returns`string`

@paramvalue`null | undefined`

@returns`null`

@paramvalue`string | null | undefined`

@returns`string | null`

## [Description](#description)

Transforms text to title case. Capitalizes the first letter of each word and transforms the rest of the word to lower case. Words are delimited by any whitespace character, such as a space, tab, or line-feed character.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

The following example shows the result of transforming various strings into title case.

```
@Component({
  selector: 'titlecase-pipe',
  imports: [TitleCasePipe],
  template: `<div>
    <p>{{ 'some string' | titlecase }}</p>
    <!-- output is expected to be "Some String" -->
    <p>{{ 'tHIs is mIXeD CaSe' | titlecase }}</p>
    <!-- output is expected to be "This Is Mixed Case" -->
    <p>{{ "it's non-trivial question" | titlecase }}</p>
    <!-- output is expected to be "It's Non-trivial Question" -->
    <p>{{ 'one,two,three' | titlecase }}</p>
    <!-- output is expected to be "One,two,three" -->
    <p>{{ 'true|false' | titlecase }}</p>
    <!-- output is expected to be "True|false" -->
    <p>{{ 'foo-vs-bar' | titlecase }}</p>
    <!-- output is expected to be "Foo-vs-bar" -->
  </div>`,
})
export class TitleCasePipeComponent {}
```

Jump to details

----
url: https://angular.dev/api/aria/grid/GRID_ROW
----

```
    
      const GRID_ROW: InjectionToken<GridRow>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/InputOptionsWithoutTransform
----

```
    
      type InputOptionsWithoutTransform<T> = Omit<InputOptions<T, T>, 'transform'> & {transform?: undefined}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/ActivatedRouteSnapshot
----

## [API](/api/router/ActivatedRouteSnapshot#api)

```
    
      class ActivatedRouteSnapshot {}
    
    
```

@paramurl`UrlSegment[]`

The URL segments matched by this route

@paramparams`Params`

The matrix parameters scoped to this route.

You can compute all params (or data) in the router state or to get params outside of an activated component by traversing the [`RouterState`](/api/router/RouterState) tree as in the following example:

```
collectRouteParams(router: Router) {
  let params = {};
  let stack: ActivatedRouteSnapshot[] = [router.routerState.snapshot.root];
  while (stack.length > 0) {
    const route = stack.pop()!;
    params = {...params, ...route.params};
    stack.push(...route.children);
  }
  return params;
}
```

@paramqueryParams`Params`

The query parameters shared by all the routes

@paramfragment`string | null`

The URL fragment shared by all the routes

@paramdata`Data`

The static and resolved data of this route

@paramoutlet`string`

The outlet name of the route

@paramcomponent`Type<any> | null`

The component of the route

@paramrouteConfig`Route | null`

@paramresolve`ResolveData`

@paramenvironmentInjector`EnvironmentInjector`

@returns`ActivatedRouteSnapshot`

The configuration used to match this route \*

The result of running the route's resources function.

The resolved route title

The URL segments matched by this route

The matrix parameters scoped to this route.

You can compute all params (or data) in the router state or to get params outside of an activated component by traversing the [`RouterState`](/api/router/RouterState) tree as in the following example:

```
collectRouteParams(router: Router) {
  let params = {};
  let stack: ActivatedRouteSnapshot[] = [router.routerState.snapshot.root];
  while (stack.length > 0) {
    const route = stack.pop()!;
    params = {...params, ...route.params};
    stack.push(...route.children);
  }
  return params;
}
```

The query parameters shared by all the routes

The URL fragment shared by all the routes

The static and resolved data of this route

The outlet name of the route

The component of the route

The root of the router state

The parent of this route in the router state tree

The first child of this route in the router state tree

The children of this route in the router state tree

The path from the root of the router state tree to this route

@returns`string`

## [Description](/api/router/ActivatedRouteSnapshot#description)

Contains the information about a route associated with a component loaded in an outlet at a particular moment in time. ActivatedRouteSnapshot can also be used to traverse the router state tree.

The following example initializes a component with route information extracted from the snapshot of the root node at the time of creation.

```
@Component({templateUrl:'./my-component.html'})
class MyComponent {
  constructor(route: ActivatedRoute) {
    const id: string = route.snapshot.params.id;
    const url: string = route.snapshot.url.join('');
    const user = route.snapshot.data.user;
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/testing/resetFakeAsyncZone
----

```
function resetFakeAsyncZone(): void;
```

@returns`void`

## [Description](#description)

**IMPORTANT:** This API requires Zone.js and cannot be used with the Vitest test runner

Clears out the shared fake async zone for a test. To be called in a global `beforeEach`.

Jump to details

----
url: https://angular.dev/api/router/RouterOutletContract
----

```
    
      interface RouterOutletContract {}
    
    
```

Whether the given outlet is activated.

An outlet is considered "activated" if it has an active component.

The instance of the activated component or `null` if the outlet is not activated.

The [`Data`](/api/router/Data) of the [`ActivatedRoute`](/api/router/ActivatedRoute) snapshot.

The [`ActivatedRoute`](/api/router/ActivatedRoute) for the outlet or `null` if the outlet is not activated.

Called by the [`Router`](/api/router/Router) when the outlet should activate (create a component).

@paramactivatedRoute`ActivatedRoute`

@paramenvironmentInjector`EnvironmentInjector`

@returns`void`

A request to destroy the currently activated component.

When a [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) indicates that an [`ActivatedRoute`](/api/router/ActivatedRoute) should be removed but stored for later re-use rather than destroyed, the [`Router`](/api/router/Router) will call `detach` instead.

@returns`void`

Called when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to detach the subtree.

This is similar to `deactivate`, but the activated component should *not* be destroyed. Instead, it is returned so that it can be reattached later via the `attach` method.

@returns`ComponentRef<unknown>`

Called when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to re-attach a previously detached subtree.

@paramref`ComponentRef<unknown>`

@paramactivatedRoute`ActivatedRoute`

@returns`void`

Emits an activate event when a new component is instantiated

Emits a deactivate event when a component is destroyed.

Emits an attached component instance when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to re-attach a previously detached subtree.

Emits a detached component instance when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to detach the subtree.

Used to indicate that the outlet is able to bind data from the [`Router`](/api/router/Router) to the outlet component's inputs.

When this is `undefined` or `false` and the developer has opted in to the feature using [`withComponentInputBinding`](/api/router/withComponentInputBinding), a warning will be logged in dev mode if this outlet is used in the application.

## [Description](#description)

An interface that defines the contract for developing a component outlet for the [`Router`](/api/router/Router).

An outlet acts as a placeholder that Angular dynamically fills based on the current router state.

A router outlet should register itself with the [`Router`](/api/router/Router) via [`ChildrenOutletContexts#onChildOutletCreated`](/api/router/ChildrenOutletContexts#onChildOutletCreated) and unregister with [`ChildrenOutletContexts#onChildOutletDestroyed`](/api/router/ChildrenOutletContexts#onChildOutletDestroyed). When the [`Router`](/api/router/Router) identifies a matched [`Route`](/api/router/Route), it looks for a registered outlet in the [`ChildrenOutletContexts`](/api/router/ChildrenOutletContexts) and activates it.

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/communications
----

Enabling service worker support does more than just register the service worker; it also provides services you can use to interact with the service worker and control the caching of your application.

## [`SwUpdate` service](#swupdate-service)

The [`SwUpdate`](/api/service-worker/SwUpdate) service gives you access to events that indicate when the service worker discovers and installs an available update for your application.

The [`SwUpdate`](/api/service-worker/SwUpdate) service supports three separate operations:

* Receiving notifications when an updated version is *detected* on the server, *installed and ready* to be used locally or when an *installation fails*.
* Asking the service worker to check the server for new updates.
* Asking the service worker to activate the latest version of the application for the current tab.

### [Version updates](#version-updates)

The `versionUpdates` is an `Observable` property of [`SwUpdate`](/api/service-worker/SwUpdate) and emits four event types:

| Event types                                                                            | Details                                                                                                                                                                     |
| :------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`VersionDetectedEvent`](/api/service-worker/VersionDetectedEvent)                     | Emitted when the service worker has detected a new version of the app on the server and is about to start downloading it.                                                   |
| [`NoNewVersionDetectedEvent`](/api/service-worker/NoNewVersionDetectedEvent)           | Emitted when the service worker has checked the version of the app on the server and did not find a new version.                                                            |
| [`VersionReadyEvent`](/api/service-worker/VersionReadyEvent)                           | Emitted when a new version of the app is available to be activated by clients. It may be used to notify the user of an available update or prompt them to refresh the page. |
| [`VersionInstallationFailedEvent`](/api/service-worker/VersionInstallationFailedEvent) | Emitted when the installation of a new version failed. It may be used for logging/monitoring purposes.                                                                      |

### log-update.service.ts

```
@Service()
export class LogUpdateService {
  private updates = inject(SwUpdate);
  constructor() {
    this.updates.versionUpdates.subscribe((evt) => {
      switch (evt.type) {
        case 'VERSION_DETECTED':
          console.log(`Downloading new app version: ${evt.version.hash}`);
          break;
        case 'VERSION_READY':
          console.log(`Current app version: ${evt.currentVersion.hash}`);
          console.log(`New app version ready for use: ${evt.latestVersion.hash}`);
          break;
        case 'VERSION_INSTALLATION_FAILED':
          console.log(`Failed to install app version '${evt.version.hash}': ${evt.error}`);
          break;
      }
    });
  }
}
```

### [Checking for updates](#checking-for-updates)

It's possible to ask the service worker to check if any updates have been deployed to the server. The service worker checks for updates during initialization and on each navigation request —that is, when the user navigates from a different address to your application. However, you might choose to manually check for updates if you have a site that changes frequently or want updates to happen on a schedule.

Do this with the `checkForUpdate()` method:

### check-for-update.service.ts

```
import {ApplicationRef, inject, Service} from '@angular/core';
import {SwUpdate} from '@angular/service-worker';
import {concat, interval, first} from 'rxjs';

@Service()
export class CheckForUpdateService {
  private appRef = inject(ApplicationRef);
  private updates = inject(SwUpdate);

  constructor() {
    // Allow the app to stabilize first, before starting
    // polling for updates with `interval()`.
    const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true));
    const everySixHours$ = interval(6 * 60 * 60 * 1000);
    const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);

    everySixHoursOnceAppIsStable$.subscribe(async () => {
      try {
        const updateFound = await this.updates.checkForUpdate();
        console.log(updateFound ? 'A new version is available.' : 'Already on the latest version.');
      } catch (err) {
        console.error('Failed to check for updates:', err);
      }
    });
  }
}
```

This method returns a `Promise<boolean>` which indicates if an update is available for activation. The check might fail, which will cause a rejection of the `Promise`.

### Stabilization and service worker registration

In order to avoid negatively affecting the initial rendering of the page, by default the Angular service worker service waits for up to 30 seconds for the application to stabilize before registering the ServiceWorker script.

Constantly polling for updates, for example, with [setInterval()](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) or RxJS' [interval()](https://rxjs.dev/api/index/function/interval), prevents the application from stabilizing and the ServiceWorker script is not registered with the browser until the 30 seconds upper limit is reached.

This is true for any kind of polling done by your application. Check the [isStable](api/core/ApplicationRef#isStable) documentation for more information.

Avoid that delay by waiting for the application to stabilize first, before starting to poll for updates, as shown in the preceding example. Alternatively, you might want to define a different [registration strategy](api/service-worker/SwRegistrationOptions#registrationStrategy) for the ServiceWorker.

### [Updating to the latest version](#updating-to-the-latest-version)

You can update an existing tab to the latest version by reloading the page as soon as a new version is ready. To avoid disrupting the user's progress, it is generally a good idea to prompt the user and let them confirm that it is OK to reload the page and update to the latest version:

### prompt-update.service.ts

```
@Service()
export class PromptUpdateService {
  constructor() {
    const swUpdate = inject(SwUpdate);
    swUpdate.versionUpdates
      .pipe(filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'))
      .subscribe((evt) => {
        if (promptUser(evt)) {
          // Reload the page to update to the latest version.
          document.location.reload();
        }
      });
  }
}
```

### Safety of updating without reloading

Calling `activateUpdate()` updates a tab to the latest version without reloading the page, but this could break the application.

Updating without reloading can create a version mismatch between the application shell and other page resources, such as lazy-loaded chunks, whose filenames may change between versions.

You should only use `activateUpdate()`, if you are certain it is safe for your specific use case.

### [Handling an unrecoverable state](#handling-an-unrecoverable-state)

In some cases, the version of the application used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload.

For example, imagine the following scenario:

1. A user opens the application for the first time and the service worker caches the latest version of the application. Assume the application's cached assets include `index.html`, `main.<main-hash-1>.js` and `lazy-chunk.<lazy-hash-1>.js`.

2. The user closes the application and does not open it for a while.

3. After some time, a new version of the application is deployed to the server. This newer version includes the files `index.html`, `main.<main-hash-2>.js` and `lazy-chunk.<lazy-hash-2>.js`.

**IMPORTANT:** The hashes are different now, because the content of the files changed. The old version is no longer available on the server.

1. In the meantime, the user's browser decides to evict `lazy-chunk.<lazy-hash-1>.js` from its cache. Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.

2. The user opens the application again. The service worker serves the latest version known to it at this point, namely the old version (`index.html` and `main.<main-hash-1>.js`).

3. At some later point, the application requests the lazy bundle, `lazy-chunk.<lazy-hash-1>.js`.

4. The service worker is unable to find the asset in the cache (remember that the browser evicted it). Nor is it able to retrieve it from the server (because the server now only has `lazy-chunk.<lazy-hash-2>.js` from the newer version).

In the preceding scenario, the service worker is not able to serve an asset that would normally be cached. That particular application version is broken and there is no way to fix the state of the client without reloading the page. In such cases, the service worker notifies the client by sending an [`UnrecoverableStateEvent`](/api/service-worker/UnrecoverableStateEvent) event. Subscribe to [`SwUpdate#unrecoverable`](/api/service-worker/SwUpdate#unrecoverable) to be notified and handle these errors.

### handle-unrecoverable-state.service.ts

```
@Service()
export class HandleUnrecoverableStateService {
  private updates = inject(SwUpdate);
  constructor() {
    this.updates.unrecoverable.subscribe((event) => {
      notifyUser(
        'An error occurred that we cannot recover from:\n' +
          event.reason +
          '\n\nPlease reload the page.',
      );
    });
  }
}
```

## [More on Angular service workers](#more-on-angular-service-workers)

You might also be interested in the following:

----
url: https://angular.dev/api/common/IMAGE_LOADER
----

## [API](/api/common/IMAGE_LOADER#api)

```
    
      const IMAGE_LOADER: InjectionToken<ImageLoader>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/reflectComponentType
----

```
function reflectComponentType<C>(component: Type<C>): ComponentMirror<C> | null;
```

@paramcomponent`Type<C>`

Component class reference.

@returns`ComponentMirror<C> | null`

An object that allows to retrieve component metadata.

## [Usage Notes](#usage-notes)

The example below demonstrates how to use the function and how the fields of the returned object map to the component metadata.

```
@Component({
  selector: 'foo-component',
  template: `
    <ng-content></ng-content>
    <ng-content select="content-selector-a"></ng-content>
  `,
})
class FooComponent {
  @Input('inputName') inputPropName: string;
  @Output('outputName') outputPropName = new EventEmitter<void>();
}

const mirror = reflectComponentType(FooComponent);
expect(mirror.type).toBe(FooComponent);
expect(mirror.selector).toBe('foo-component');
expect(mirror.isStandalone).toBe(true);
expect(mirror.inputs).toEqual([{propName: 'inputName', templateName: 'inputPropName'}]);
expect(mirror.outputs).toEqual([{propName: 'outputName', templateName: 'outputPropName'}]);
expect(mirror.ngContentSelectors).toEqual([
  '*',                 // first `<ng-content>` in a template, the selector defaults to `*`
  'content-selector-a' // second `<ng-content>` in a template
]);
```

Jump to details

----
url: https://angular.dev/api/forms/signals/SignalFormsConfig
----

```
    
      interface SignalFormsConfig {}
    
    
```

A map of CSS class names to predicate functions that determine when to apply them.

Jump to details

----
url: https://angular.dev/api/router/RouterModule
----

## [API](/api/router/RouterModule#api)

```
    
      class RouterModule {}
    
    
```

Creates and configures a module with all the router providers and directives. Optionally sets up an application listener to perform an initial navigation.

When registering the NgModule at the root, import as follows:

```
@NgModule({
  imports: [RouterModule.forRoot(ROUTES)]
})
class MyNgModule {}
```

@paramroutes`Routes`

An array of [`Route`](/api/router/Route) objects that define the navigation paths for the application.

@paramconfig`ExtraOptions | undefined`

An [`ExtraOptions`](/api/router/ExtraOptions) configuration object that controls how navigation is performed.

@returns`ModuleWithProviders<RouterModule>`

Creates a module with all the router directives and a provider registering routes, without creating a new Router service. When registering for submodules and lazy-loaded submodules, create the NgModule as follows:

```
@NgModule({
  imports: [RouterModule.forChild(ROUTES)]
})
class MyNgModule {}
```

@paramroutes`Routes`

An array of [`Route`](/api/router/Route) objects that define the navigation paths for the submodule.

@returns`ModuleWithProviders<RouterModule>`

## [Description](/api/router/RouterModule#description)

Adds directives and providers for in-app navigation among views defined in an application. Use the Angular [`Router`](/api/router/Router) service to declaratively specify application states and manage state transitions.

You can import this NgModule multiple times, once for each lazy-loaded bundle. However, only one [`Router`](/api/router/Router) service can be active. To ensure this, there are two ways to register routes when importing this module:

* The `forRoot()` method creates an [`NgModule`](/api/core/NgModule) that contains all the directives, the given routes, and the [`Router`](/api/router/Router) service itself.
* The `forChild()` method creates an [`NgModule`](/api/core/NgModule) that contains all the directives and the given routes, but does not include the [`Router`](/api/router/Router) service.

Jump to details

----
url: https://angular.dev/best-practices/profiling-with-chrome-devtools
----

Angular integrates with the [Chrome DevTools extensibility API](https://developer.chrome.com/docs/devtools/performance/extension) to present framework-specific data and insights directly in the [Chrome DevTools performance panel](https://developer.chrome.com/docs/devtools/performance/overview).

With the integration enabled, you can [record a performance profile](https://developer.chrome.com/docs/devtools/performance#record) containing two sets of data:

* Standard performance entries based on Chrome's understanding of your code executing in a browser, and
* Angular-specific entries contributed by the framework's runtime.

Both sets of data are presented together on the same tab, but on separate tracks:

Angular-specific data are expressed in terms of framework concepts (components, change detection, lifecycle hooks, etc.) alongside lower-level function and method calls captured by a browser. These two data sets are correlated, and you can switch between the different views and level of details.

You can use the Angular track to better understand how your code runs in the browser, including:

* Determining whether a given code block is part of the Angular application, or whether it belongs to another script running on the same page.
* Identifying performance bottlenecks and attribute those to specific components or services.
* Gaining deeper insight into Angular's inner working with a visual breakdown of each change detection cycle.

## [Recording a profile](#recording-a-profile)

### [Enable integration](#enable-integration)

You can enable Angular profiling in one of two ways:

1. Run [`ng.enableProfiling()`](api/core/enableProfiling) in Chrome's console panel, or
2. Include a call to [`enableProfiling()`](api/core/enableProfiling) in your application startup code (imported from `@angular/core`).

**NOTE:** Angular profiling works exclusively in development mode.

Here is an example of how you can enable the integration in the application bootstrap to capture all possible events:

```
import {enableProfiling} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {MyApp} from './my-app';

// Turn on profiling *before* bootstrapping your application
// in order to capture all of the code run on start-up.
enableProfiling();
bootstrapApplication(MyApp);
```

### [Record a profile](#record-a-profile)

Use the **Record** button in the Chrome DevTools performance panel:

See the [Chrome DevTools documentation](https://developer.chrome.com/docs/devtools/performance#record) for more details on recording profiles.

## [Open a component in Angular DevTools](#open-a-component-in-angular-devtools)

After recording a profile, select a component event in the **Angular** track. The **Summary** tab can include a **Component** link that uses the `angular-devtools://component/...` URL scheme.

Click the link to open Angular DevTools and select the matching component in the **Components** tab. This helps you move from a browser-level profile to the component state and metadata for a selected event.

**NOTE:** Opening component links requires Angular DevTools for Chrome and Chrome's experimental `chrome://flags/#enable-devtools-deep-link-via-extensibility-api` flag.

## [Interpreting a recorded profile](#interpreting-a-recorded-profile)

You can use the "Angular" custom track to quickly identify and diagnose performance issues. The following sections describe some common profiling scenarios.

### [Differentiating between your Angular application and other tasks on the same page](#differentiating-between-your-angular-application-and-other-tasks-on-the-same-page)

As Angular and Chrome data are presented on the separate but correlated tracks, you can see when Angular's application code is executed as opposed to some other browser processing (typically layout and paint) or other scripts running on the same page (in this case the custom Angular track does not have any data):

This allows you to determine whether further investigations should focus on the Angular application code or on other parts of your codebase or dependencies.

### [Color-coding](#color-coding)

Angular uses colors in the flame chart graph to distinguish tasks types:

* 🟦 Blue represents TypeScript code written by the application developer (for example: services, component constructors and lifecycle hooks, etc.).
* 🟪 Purple represents templates code written by the application developer and transformed by the Angular compiler.
* 🟩 Green represents entry points to the application code and identifies *reasons* for executing code.

The following examples illustrate the described color-coding in various, real-life recordings.

#### [Example: Application bootstrapping](#example-application-bootstrapping)

The application bootstrap process usually consists of:

* Triggers marked in blue, such as the call to the [`bootstrapApplication`](/api/platform-browser/bootstrapApplication), instantiation of the root component, and initial change detection
* Various DI services instantiated during bootstrap, marked in green.

#### [Example: Component execution](#example-component-execution)

One component processing is typically represented as an entry point (blue) followed by its template execution (purple). A template might, in turn, trigger instantiation of directives and execution of lifecycle hooks (green):

#### [Example: Change detection](#example-change-detection)

A change detection cycle usually consists of one or more data synchronization passes (blue), where each pass traverses a subset of components.

With this data visualization, it is possible to immediately identify components that were involved in the change detection and which were skipped (typically the `OnPush` components that were not marked dirty).

Additionally, you can inspect the number of synchronization passes for one change detection. Having more than one synchronization pass suggest that state is updated during change detection. You should avoid this, as it slows down page updates and can even result in infinite loops in the worst cases.

----
url: https://angular.dev/errors/NG8023
----

Two or more [components](guide/components) in the compilation scope match the same element in a template. Because Angular can associate only one component with a given element, selectors must be unique enough to prevent ambiguity.

**NOTE:** This is the build-time equivalent of the runtime error [NG0300: Selector Collision](errors/NG0300). Detecting this at compile time means the error surfaces immediately.

```
import {Component} from '@angular/core';

@Component({
  selector: '[stroked-button]',
  template: '',
})
export class StrokedBtn {}

@Component({
  selector: '[raised-button]',
  template: '',
})
export class RaisedBtn {}

@Component({
  selector: 'app-root',
  // NG8023: StrokedBtn and RaisedBtn both match this element.
  template: `<button stroked-button raised-button></button>`,
  imports: [StrokedBtn, RaisedBtn],
})
export class App {}
```

## [Debugging the error](#debugging-the-error)

Use the element name from the error message to search for places where you're using the same selector declaration in your codebase:

```
@Component({
  selector: 'YOUR_STRING',
…
})
```

Ensure that each component has a unique CSS selector. This will guarantee that Angular renders the component you expect.

If you're having trouble finding multiple components with this selector tag name, check for components from imported component libraries, such as Angular Material. Make sure you're following the [best practices](style-guide#choosing-component-selectors) for your selectors to prevent collisions.

----
url: https://angular.dev/guide/i18n/prepare
----

To prepare your project for translation, complete the following actions.

* Use the `i18n` attribute to mark text in component templates
* Use the `i18n-` attribute to mark attribute text strings in component templates
* Use the [`$localize`](/api/localize/init/$localize) tagged message string to mark text strings in component code

## [Mark text in component template](#mark-text-in-component-template)

In a component template, the i18n metadata is the value of the `i18n` attribute.

```
<element i18n="{i18n_metadata}">{string_to_translate}</element>
```

Use the `i18n` attribute to mark a static text message in your component templates for translation. Place it on every element tag that contains fixed text you want to translate.

**HELPFUL:** The `i18n` attribute is a custom attribute that the Angular tools and compilers recognize.

### [`i18n` example](#i18n-example)

The following `<h1>` tag displays a simple English language greeting, "Hello i18n!".

### app.component.html

```
<h1>Hello i18n!</h1>
```

To mark the greeting for translation, add the `i18n` attribute to the `<h1>` tag.

### app.component.html

```
<h1 i18n>Hello i18n!</h1>
```

### [using conditional statement with `i18n`](#using-conditional-statement-with-i18n)

The following `<div>` tag will display translated text as part of `div` and `aria-label` based on toggle status

### app.component.html

```
<button type="button" (click)="toggleDisplay()">Toggle</button>
<div i18n [attr.aria-label]="toggleAriaLabel()">{{ toggle() }}</div>
```

### app.component.ts

```
import {Component, computed, signal} from '@angular/core';
import {$localize} from '@angular/localize/init';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  minutes = 0;
  gender = 'female';
  fly = true;
  logo = '${this.baseUrl}/angular.svg';
  readonly toggle = signal(false);
  readonly toggleAriaLabel = computed(() => {
    return this.toggle()
      ? $localize`:Toggle Button|A button to toggle status:Show`
      : $localize`:Toggle Button|A button to toggle status:Hide`;
  });

  inc(i: number) {
    this.minutes = Math.min(5, Math.max(0, this.minutes + i));
  }
  male() {
    this.gender = 'male';
  }
  female() {
    this.gender = 'female';
  }
  other() {
    this.gender = 'other';
  }
  toggleDisplay() {
    this.toggle.update((toggle) => !toggle);
  }
}
```

### [Translate inline text without HTML element](#translate-inline-text-without-html-element)

Use the `<ng-container>` element to associate a translation behavior for specific text without changing the way text is displayed.

**HELPFUL:** Each HTML element creates a new DOM element. To avoid creating a new DOM element, wrap the text in an `<ng-container>` element. The following example shows the `<ng-container>` element transformed into a non-displayed HTML comment.

```
<ng-container i18n>I don't output any element</ng-container>
```

### [Name the interpolation placeholder](#name-the-interpolation-placeholder)

By default, Angular generates a placeholder name for each interpolation in a translated message. To give it a meaningful name that helps translators understand the context, add an `//i18n(ph="name")` comment inside the interpolation.

```
<element i18n>{{ expression //i18n(ph="placeholder_name") }}</element>
```

For example:

```
<p i18n>Hello, {{ username //i18n(ph="name") }}!</p>
```

This is the template equivalent of naming a placeholder in component code with [`$localize`](api/localize/init/$localize "$localize | init - localize - API  | Angular"):

```
$localize`Hello, ${username}:name:!`;
```

## [Mark element attributes for translations](#mark-element-attributes-for-translations)

In a component template, the i18n metadata is the value of the `i18n-{attribute_name}` attribute.

```
<element i18n-{attribute_name}="{i18n_metadata}" {attribute_name}="{attribute_value}" />
```

The attributes of HTML elements include text that should be translated along with the rest of the displayed text in the component template.

Use `i18n-{attribute_name}` with any attribute of any element and replace `{attribute_name}` with the name of the attribute. Use the following syntax to assign a meaning, description, and custom ID.

```
i18n-{attribute_name}="{meaning}|{description}@@{id}"
```

### [`i18n-title` example](#i18n-title-example)

To translate the title of an image, review this example. The following example displays an image with a `title` attribute.

### app.component.html

```
<img [src]="logo" title="Angular logo" alt="Angular logo" />
```

To mark the title attribute for translation, complete the following action.

Add the `i18n-title` attribute

The following example displays how to mark the `title` attribute on the `img` tag by adding `i18n-title`.

### app.component.html

```
<img [src]="logo" i18n-title title="Angular logo" alt="Angular logo" />
```

## [Mark text in component code](#mark-text-in-component-code)

In component code, the translation source text and the metadata are surrounded by backtick (`` ` ``) characters.

Use the [`$localize`](api/localize/init/$localize "$localize | init - localize - API  | Angular") tagged message string to mark a string in your code for translation.

```
$localize`string_to_translate`;
```

The i18n metadata is surrounded by colon (`:`) characters and prepends the translation source text.

```
$localize`:{i18n_metadata}:string_to_translate`;
```

### [Include interpolated text](#include-interpolated-text)

Include [interpolations](guide/templates/binding#render-dynamic-text-with-text-interpolation) in a [`$localize`](api/localize/init/$localize "$localize | init - localize - API  | Angular") tagged message string.

```
$localize`string_to_translate ${variable_name}`;
```

### [Name the interpolation placeholder](#name-the-interpolation-placeholder-in-code)

```
$localize`string_to_translate ${variable_name}:placeholder_name:`;
```

### [Conditional syntax for translations](#conditional-syntax-for-translations)

```
return this.show ? $localize`Show Tabs` : $localize`Hide tabs`;
```

## [i18n metadata for translation](#i18n-metadata-for-translation)

```
{meaning}|{description}@@{custom_id}
```

The following parameters provide context and additional information to reduce confusion for your translator.

| Metadata parameter | Details                                                               |
| :----------------- | :-------------------------------------------------------------------- |
| Custom ID          | Provide a custom identifier                                           |
| Description        | Provide additional information or context                             |
| Meaning            | Provide the meaning or intent of the text within the specific context |

For additional information about custom IDs, see [Manage marked text with custom IDs](guide/i18n/manage-marked-text "Manage marked text with custom IDs | Angular").

### [Add helpful descriptions and meanings](#add-helpful-descriptions-and-meanings)

To translate a text message accurately, provide additional information or context for the translator.

Add a *description* of the text message as the value of the `i18n` attribute or [`$localize`](api/localize/init/$localize "$localize | init - localize - API  | Angular") tagged message string.

The following example shows the value of the `i18n` attribute.

### app.component.html

```
<h1 i18n="An introduction header for this sample">Hello i18n!</h1>
```

The following example shows the value of the [`$localize`](api/localize/init/$localize "$localize | init - localize - API  | Angular") tagged message string with a description.

```
$localize`:An introduction header for this sample:Hello i18n!`;
```

The translator may also need to know the meaning or intent of the text message within this particular application context, in order to translate it the same way as other text with the same meaning. Start the `i18n` attribute value with the *meaning* and separate it from the *description* with the `|` character: `{meaning}|{description}`.

#### [`h1` example](#h1-example)

For example, you may want to specify that the `<h1>` tag is a site header that you need translated the same way, whether it is used as a header or referenced in another section of text.

The following example shows how to specify that the `<h1>` tag must be translated as a header or referenced elsewhere.

### app.component.html

```
<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>
```

The result is any text marked with `site header`, as the *meaning* is translated exactly the same way.

The following code example shows the value of the [`$localize`](api/localize/init/$localize "$localize | init - localize - API  | Angular") tagged message string with a meaning and a description.

```
$localize`:site header|An introduction header for this sample:Hello i18n!`;
```

### How meanings control text extraction and merges

The Angular extraction tool generates a translation unit entry for each `i18n` attribute in a template. The Angular extraction tool assigns each translation unit a unique ID based on its source text and *meaning*. The *description* does not affect the ID.

**HELPFUL:** For more information about the Angular extraction tool, see [Work with translation files](guide/i18n/translation-files).

The same text elements with different *meanings* are extracted with different IDs. For example, if the word "right" uses the following two definitions in two different locations, the word is translated differently and merged back into the application as different translation entries.

* `correct` as in "you are right"
* `direction` as in "turn right"

If the same text elements meet the following conditions, the text elements are extracted only once and use the same ID.

* Same meaning or definition
* Different descriptions

That one translation entry is merged back into the application wherever the same text elements appear.

## [ICU expressions](#icu-expressions)

ICU expressions help you mark alternate text in component templates to meet conditions. An ICU expression includes a component property, an ICU clause, and the case statements surrounded by open curly brace (`{`) and close curly brace (`}`) characters.

```
{ component_property, icu_clause, case_statements }
```

The component property defines the variable. An ICU clause defines the type of conditional text.

| ICU clause                                                                                                                                                    | Details                                                             |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------ |
| [`plural`](guide/i18n/prepare#mark-plurals "Mark plurals - Prepare component for translation \| Angular")                                                     | Mark the use of plural numbers                                      |
| [`select`](guide/i18n/prepare#mark-alternates-and-nested-expressions "Mark alternates and nested expressions - Prepare templates for translation \| Angular") | Mark choices for alternate text based on your defined string values |

To simplify translation, use International Components for Unicode clauses (ICU clauses) with regular expressions.

**HELPFUL:** The ICU clauses adhere to the [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages "ICU Message Format - ICU Documentation | Unicode | GitHub") specified in the [CLDR pluralization rules](https://cldr.unicode.org/index/cldr-spec/plural-rules "Plural Rules | CLDR - Unicode Common Locale Data Repository | Unicode").

### [Mark plurals](#mark-plurals)

Different languages have different pluralization rules that increase the difficulty of translation. Because other locales express cardinality differently, you may need to set pluralization categories that do not align with English. Use the `plural` clause to mark expressions that may not be meaningful if translated word-for-word.

```
{ component_property, plural, pluralization_categories }
```

After the pluralization category, enter the default text (English) surrounded by open curly brace (`{`) and close curly brace (`}`) characters.

```
pluralization_category { }
```

The following pluralization categories are available for English and may change based on the locale.

| Pluralization category | Details                    | Example             |
| :--------------------- | :------------------------- | :------------------ |
| `zero`                 | Quantity is zero           | `=0 { }` `zero { }` |
| `one`                  | Quantity is 1              | `=1 { }` `one { }`  |
| `two`                  | Quantity is 2              | `=2 { }` `two { }`  |
| `few`                  | Quantity is 2 or more      | `few { }`           |
| `many`                 | Quantity is a large number | `many { }`          |
| `other`                | The default quantity       | `other { }`         |

If none of the pluralization categories match, Angular uses `other` to match the standard fallback for a missing category.

```
other { default_quantity }
```

**HELPFUL:** For more information about pluralization categories, see [Choosing plural category names](https://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Choosing-Plural-Category-Names "Choosing Plural Category Names - Plural Rules | CLDR - Unicode Common Locale Data Repository | Unicode") in the [CLDR - Unicode Common Locale Data Repository](https://cldr.unicode.org "Unicode CLDR Project").

### Background: Locales may not support some pluralization categories

Many locales don't support some of the pluralization categories. The default locale (`en-US`) uses a very simple `plural()` function that doesn't support the `few` pluralization category. Another locale with a simple `plural()` function is `es`. The following code example shows the [en-US `plural()`](https://github.com/angular/angular/blob/ecffc3557fe1bff9718c01277498e877ca44588d/packages/core/src/i18n/locale_en.ts#L14-L18 "Line 14 to 18 - angular/packages/core/src/i18n/locale_en.ts | angular/angular | GitHub") function.

```
function plural(n: number): number {
  let i = Math.floor(Math.abs(n)),
    v = n.toString().replace(/^[^.]*\.?/, '').length;
  if (i === 1 && v === 0) return 1;
  return 5;
}
```

The `plural()` function only returns 1 (`one`) or 5 (`other`). The `few` category never matches.

#### [`minutes` example](#minutes-example)

If you want to display the following phrase in English, where `x` is a number.

```
updated x minutes ago
```

And you also want to display the following phrases based on the cardinality of `x`.

```
updated just now
```

```
updated one minute ago
```

Use HTML markup and [interpolations](guide/templates/binding#render-dynamic-text-with-text-interpolation). The following code example shows how to use the `plural` clause to express the previous three situations in a `<span>` element.

### app.component.html

```
<span i18n
  >Updated
  {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span
>
```

Review the following details in the previous code example.

| Parameters                        | Details                                                                                                               |
| :-------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| `minutes`                         | The first parameter specifies the component property is `minutes` and determines the number of minutes.               |
| `plural`                          | The second parameter specifies the ICU clause is `plural`.                                                            |
| `=0 {just now}`                   | For zero minutes, the pluralization category is `=0`. The value is `just now`.                                        |
| `=1 {one minute}`                 | For one minute, the pluralization category is `=1`. The value is `one minute`.                                        |
| `other {{{minutes}} minutes ago}` | For any unmatched cardinality, the default pluralization category is `other`. The value is `{{minutes}} minutes ago`. |

`{{minutes}}` is an [interpolation](guide/templates/binding#render-dynamic-text-with-text-interpolation).

### [Mark alternates and nested expressions](#mark-alternates-and-nested-expressions)

The `select` clause marks choices for alternate text based on your defined string values.

```
{ component_property, select, selection_categories }
```

Translate all of the alternates to display alternate text based on the value of a variable.

After the selection category, enter the text (English) surrounded by open curly brace (`{`) and close curly brace (`}`) characters.

```
selection_category { text }
```

Different locales have different grammatical constructions that increase the difficulty of translation. Use HTML markup. If none of the selection categories match, Angular uses `other` to match the standard fallback for a missing category.

```
other { default_value }
```

#### [`gender` example](#gender-example)

If you want to display the following phrase in English.

```
The author is other
```

And you also want to display the following phrases based on the `gender` property of the component.

```
The author is female
```

```
The author is male
```

The following code example shows how to bind the `gender` property of the component and use the `select` clause to express the previous three situations in a `<span>` element.

The `gender` property binds the outputs to each of following string values.

| Value  | English value |
| :----- | :------------ |
| female | `female`      |
| male   | `male`        |
| other  | `other`       |

The `select` clause maps the values to the appropriate translations. The following code example shows `gender` property used with the select clause.

### app.component.html

```
<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
```

#### [`gender` and `minutes` example](#gender-and-minutes-example)

Combine different clauses together, such as the `plural` and `select` clauses. The following code example shows nested clauses based on the `gender` and `minutes` examples.

### app.component.html

```
<span i18n
  >Updated:
  {minutes, plural,
    =0 {just now}
    =1 {one minute ago}
    other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}
  }
</span>
```

## [What's next](#whats-next)

----
url: https://angular.dev/api/cdk/drag-drop/createDropListRef
----

## [API](/api/cdk/drag-drop/createDropListRef#api)

```
function createDropListRef<T = unknown>(
  injector: Injector,
  element: any,
): DropListRef<T>;
```

@paraminjector`Injector`

Injector used to resolve dependencies.

@paramelement`any`

Element to which to attach the drop list functionality.

@returns`DropListRef<T>`

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/RxResourceOptions
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](/api/core/rxjs-interop/RxResourceOptions#api)

```
    
      interface RxResourceOptions<T, R> extends BaseResourceOptions<T, R> {}
    
    
```

A function that returns the `Observable` to load the resource's value from.

This Observable must eventually emit a value or an error before it completes — a resource needs one of those two things to know what to show. If the Observable completes without ever emitting anything (for example because of `catchError(() => EMPTY)`), Angular throws `RESOURCE_COMPLETED_BEFORE_PRODUCING_VALUE` (`NG0991`), since it has nothing to give the resource.

A reactive function which determines the request to be made. Whenever the request changes, the loader will be triggered to fetch a new value for the resource.

If a params function isn't provided, the loader won't rerun unless the resource is reloaded.

The value which will be returned from the resource when a server value is unavailable, such as when the resource is still loading.

Equality function used to compare the return value of the loader.

Overrides the `Injector` used by `resource`.

Identifier used to cache the resource data in the `TransferState` during server-side rendering and to retrieve it on the client side. This value value needs to be identical for both the client and server.

Jump to details

----
url: https://angular.dev/errors/NG0205
----

This error occurs when you attempt to retrieve a service from an injector that has already been destroyed. This typically happens when code tries to access dependencies after a component, directive, or module has been destroyed.

## [Common scenarios](#common-scenarios)

### [Accessing services in callbacks after destruction](#accessing-services-in-callbacks-after-destruction)

When a component is destroyed, its injector is also destroyed. If an async callback later tries to access services, this error occurs:

```
@Component(/* ... */)
export class UserProfile implements OnInit {
  private userClient = inject(UserClient);

  ngOnInit() {
    setTimeout(() => {
      // ERROR: If component was destroyed before timeout fires,
      // the injector is no longer available
      this.userClient.fetchData();
    }, 5000);
  }
}
```

### [Accessing services after unsubscribing](#accessing-services-after-unsubscribing)

Similar issues occur with observables if cleanup happens in the wrong order:

```
@Component(/* ... */)
export class DataView implements OnDestroy {
  private dataStore = inject(DataStore);

  ngOnDestroy() {
    // Problematic: attempting to use the injector during destruction
    // after other cleanup may have occurred
    this.dataStore.cleanup();
  }
}
```

## [Debugging the error](#debugging-the-error)

To fix this error:

1. **Check async operations** — Ensure callbacks, promises, and subscriptions are cancelled when the component is destroyed. Use [`takeUntilDestroyed()`](/api/core/rxjs-interop/takeUntilDestroyed) or [`DestroyRef`](/api/core/DestroyRef) for cleanup.

2. **Capture dependencies early** — Store references to services in class fields rather than accessing the injector in callbacks.

3. **Guard against destroyed state** — For operations that might outlive the component, check if the component is still active before accessing services.

```
@Component(/* ... */)
export class UserProfile implements OnInit {
  private destroyRef = inject(DestroyRef);
  private userClient = inject(UserClient);

  ngOnInit() {
    // Use takeUntilDestroyed to automatically cancel when destroyed
    interval(5000)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => {
        this.userClient.fetchData();
      });
  }
}
```

The stack trace indicates where the destroyed injector was accessed. Work backwards to identify the async operation that outlived its component.

----
url: https://angular.dev/tools/devtools/signals
----

The **Components** tab can display a live signal graph for the selected component or directive. The graph shows how the signals, computeds, effects, and other reactive nodes in that component depend on each other, which makes it easier to understand and debug reactive data flows.

## [Open the signal graph](#open-the-signal-graph)

In the **Components** tab, select a component or directive. In the property pane header, click **Show Signal Graph**. The signal graph opens next to the component tree.

You can also jump directly to a specific signal: in the properties view, click the signal graph button next to the property, or, when the property has more than one action, open its actions menu and choose **Show in signal graph**.

The **Show Signal Graph** button appears automatically when the inspected application supports the signal graph API — there is no setting to enable.

## [Read the graph](#read-the-graph)

Nodes in the graph represent the reactive nodes that participate in the selected component:

* **Signal**: a writable signal created with [`signal()`](/api/core/signal).
* **Computed**: a derived value created with [`computed()`](/api/core/computed).
* **Effect**: a side effect created with [`effect()`](/api/core/effect) or [`afterRenderEffect()`](/api/core/afterRenderEffect).
* **Linked signal**: a writable signal created with [`linkedSignal()`](/api/core/linkedSignal).
* **Template**: the component's template, which reads signals when it renders.
* **Resource**: signals that belong to a [`resource()`](/api/core/resource) or [`rxResource()`](/api/core/rxjs-interop/rxResource) are grouped into a collapsible cluster. Click **Expand** on the cluster node to see the individual nodes inside.

Lines between nodes show reactive dependencies: each node is connected to the producers it reads from (upstream) and the dependants that read it (downstream).

**TIP:** Nodes are labeled with the `debugName` of their reactive node. Giving your signals, computeds, and especially effects — which are usually not assigned to a class member — a `debugName` makes the graph much easier to read:

```
effect(
  () => {
    // ...
  },
  {debugName: 'name in graph'},
);
```

## [Search the graph](#search-the-graph)

Click the search icon in the graph toolbar to find a node by name. Angular DevTools highlights every match and lets you step through them.

## [Inspect a node](#inspect-a-node)

Click a node to open its details panel, which shows the node's current value as an expandable tree and offers the following actions:

* **View source**: navigate to the node's definition in the Sources tab (Chrome) or Debugger tab (Firefox). The action is disabled when the source location is not available.
* **Set breakpoint**: pause execution when the signal updates. Breakpoints are only available in Chromium-based browsers.
* **Watch signal**: log the signal's updates and invalidations to the browser console. Click the action again to stop watching.
* **Highlight upstream / downstream**: trace the paths of the node's dependencies or its dependants.

If the component uses no signals, Angular DevTools shows a "No signals in this component" message instead of the graph.

## [Version support](#version-support)

The signal graph requires an application built with Angular 19 or later running in development mode.

----
url: https://angular.dev/api/common/http/HttpClientJsonpModule
----

JSONP is deprecated as it can cause XSS vulnerabilities. Intent to remove in future versions of Angular.

## [API](#api)

```
    
      class HttpClientJsonpModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/NgModel
----

```
    
      class NgModel extends NgControl implements OnChanges ,OnDestroy {}
    
    
```

@paramparent`ControlContainer`

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@paramvalueAccessors`ControlValueAccessor[]`

@param\_changeDetectorRef`ChangeDetectorRef | null | undefined`

@paramcallSetDisabledState`SetDisabledStateOption | undefined`

@paraminjector`Injector | undefined`

@paramrenderer`Renderer2 | undefined`

@returns`NgModel`

Tracks the name bound to the directive. If a parent form exists, it uses this name as a key to retrieve this control's value.

Tracks whether the control is disabled.

Tracks the value bound to this directive.

Tracks the configuration options for this `ngModel` instance.

**name**: An alternative to setting the name attribute on the form control element. See the [example](api/forms/NgModel) for using [`NgModel`](/api/forms/NgModel) as a standalone control.

**standalone**: When set to true, the `ngModel` will not register itself with its parent form, and acts as if it's not in the form. Defaults to false. If no parent form exists, this option has no effect.

**updateOn**: Defines the event upon which the form control value and validity update. Defaults to 'change'. Possible values: `'change'` | `'blur'` | `'submit'`.

Event emitter for producing the `ngModelChange` event after the view model updates.

Template-driven forms handle `required` via the [`RequiredValidator`](/api/forms/RequiredValidator) directive.

This directive has a `required` input and a host binding to `[attr.required]`. It defines the source of truth for required-ness, so disable the normal control binding for it.

Returns an array that represents the path from the top-level form to this control. Each index is the string name of the control on that level.

The top-level directive for this control if present, otherwise null.

Sets the new value for the view model and emits an `ngModelChange` event.

@paramnewValue`any`

The new value emitted by `ngModelChange`.

@returns`void`

The value accessor for the control

Validator function that returns current parse errors.

Tracks last bound values to avoid unnecessary FVC updates.

@returns`void`

@paramhost`ControlDirectiveHost<undefined>`

@parambindRequired`boolean`

@returns`void`

@paramcontrol`AbstractControl<any, any, any> | null | undefined`

@returns`void`

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Creates a [`FormControl`](/api/forms/FormControl) instance from a [domain model](https://en.wikipedia.org/wiki/Domain_model) and binds it to a form control element.

The [`FormControl`](/api/forms/FormControl) instance tracks the value, user interaction, and validation status of the control and keeps the view synced with the model. If used within a parent form, the directive also registers itself with the form as a child control.

This directive is used by itself or as part of a larger form. Use the `ngModel` selector to activate it.

It accepts a domain model as an optional [`Input`](/api/core/Input). If you have a one-way binding to `ngModel` with `[]` syntax, changing the domain model's value in the component class sets the value in the view. If you have a two-way binding with `[()]` syntax (also known as 'banana-in-a-box syntax'), the value in the UI always syncs back to the domain model in your class.

To inspect the properties of the associated [`FormControl`](/api/forms/FormControl) (like the validity state), export the directive into a local template variable using `ngModel` as the key (ex: `#myVar="ngModel"`). You can then access the control using the directive's `field` property. However, the most commonly used properties (like `valid` and `dirty`) also exist on the control for direct access. See a full list of properties directly available in [`AbstractControlDirective`](/api/forms/AbstractControlDirective).

## [Exported by](#exported-by)

* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using ngModel on a standalone control](#using-ngmodel-on-a-standalone-control)

The following examples show a simple standalone control using `ngModel`:

```
import {Component} from '@angular/core';

@Component({
  selector: 'example-app',
  template: `
    <input [(ngModel)]="name" #ctrl="ngModel" required />

    <p>Value: {{ name }}</p>
    <p>Valid: {{ ctrl.valid }}</p>

    <button (click)="setValue()">Set value</button>
  `,
  standalone: false,
})
export class SimpleNgModelComp {
  name: string = '';

  setValue() {
    this.name = 'Nancy';
  }
}
```

When using the `ngModel` within `<form>` tags, you'll also need to supply a `name` attribute so that the control can be registered with the parent form under that name.

In the context of a parent form, it's often unnecessary to include one-way or two-way binding, as the parent form syncs the value for you. You access its properties by exporting it into a local template variable using `ngForm` such as (`#f="ngForm"`). Use the variable where needed on form submission.

If you do need to populate initial values into your form, using a one-way binding for `ngModel` tends to be sufficient as long as you use the exported form's value rather than the domain model's value on submit.

### [Using ngModel within a form](#using-ngmodel-within-a-form)

The following example shows controls using `ngModel` within a form:

```
import {Component} from '@angular/core';
import {NgForm} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
      <input name="first" ngModel required #first="ngModel" />
      <input name="last" ngModel />
      <button>Submit</button>
    </form>

    <p>First name value: {{ first.value }}</p>
    <p>First name valid: {{ first.valid }}</p>
    <p>Form value: {{ f.value | json }}</p>
    <p>Form valid: {{ f.valid }}</p>
  `,
  standalone: false,
})
export class SimpleFormComp {
  onSubmit(f: NgForm) {
    console.log(f.value); // { first: '', last: '' }
    console.log(f.valid); // false
  }
}
```

### [Using a standalone ngModel within a group](#using-a-standalone-ngmodel-within-a-group)

The following example shows you how to use a standalone ngModel control within a form. This controls the display of the form, but doesn't contain form data.

```
<form>
  <input name="login" ngModel placeholder="Login">
  <input type="checkbox" ngModel [ngModelOptions]="{standalone: true}"> Show more options?
</form>
<!-- form value: {login: ''} -->
```

### [Setting the ngModel `name` attribute through options](#setting-the-ngmodel-name-attribute-through-options)

The following example shows you an alternate way to set the name attribute. Here, an attribute identified as name is used within a custom form control component. To still be able to specify the NgModel's name, you must specify it using the `ngModelOptions` input instead.

```
<form>
  <my-custom-form-control name="Nancy" ngModel [ngModelOptions]="{name: 'user'}">
  </my-custom-form-control>
</form>
<!-- form value: {user: ''} -->
```

Jump to details

----
url: https://angular.dev/errors/NG0950
----

A required input was accessed but no value was bound.

This can happen when a required input is accessed too early in your directive or component. This is commonly happening when the input is read as part of class construction.

Inputs are guaranteed to be available in the `ngOnInit` lifecycle hook and afterwards.

## [Fixing the error](#fixing-the-error)

Access the required input in reactive contexts. For example, in the template itself, inside a [`computed`](/api/core/computed), or inside an effect.

Alternatively, access the input inside the `ngOnInit` lifecycle hook, or later.

----
url: https://angular.dev/api/forms/EmailValidator
----

```
    
      class EmailValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the email attribute bound to this directive.

## [Description](#description)

A directive that adds the `email` validator to controls marked with the `email` attribute. The directive is provided with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) multi-provider list.

The email validation is based on the WHATWG HTML specification with some enhancements to incorporate more RFC rules. More information can be found on the [Validators.email page](api/forms/Validators#email).

## [Exported by](#exported-by)

* `FormsModule`
* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

### [Adding an email validator](#adding-an-email-validator)

The following example shows how to add an email validator to an input attached to an ngModel binding.

```
<input type="email" name="email" ngModel email>
<input type="email" name="email" ngModel email="true">
<input type="email" name="email" ngModel [email]="true">
```

Jump to details

----
url: https://angular.dev/api/forms/NgControlStatusGroup
----

```
    
      class NgControlStatusGroup extends AbstractControlStatus {}
    
    
```

@paramcd`ControlContainer`

@returns`NgControlStatusGroup`

## [Description](#description)

Directive automatically applied to Angular form groups that sets CSS classes based on control status (valid/invalid/dirty/etc). On groups, this includes the additional class ng-submitted.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

Jump to details

----
url: https://angular.dev/api/forms/signals/FormFieldBinding
----

```
    
      interface FormFieldBinding {}
    
    
```

The HTML element on which the [`FormField`](/api/forms/signals/FormField) directive is applied.

The node injector for the element hosting this field binding.

The [`FieldState`](/api/forms/signals/FieldState) of the field bound to the [`FormField`](/api/forms/signals/FormField) directive.

Focuses this field binding.

By default, this will focus [`element`](/api/forms/signals/FormFieldBinding#element). However, custom controls can implement their own focus behavior.

@paramoptions`FocusOptions | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/DetachedRouteHandle
----

```
    
      type DetachedRouteHandle = {}
    
    
```

## [Description](#description)

Represents the detached route tree.

This is an opaque value the router will give to a custom route reuse strategy to store and retrieve later on.

Jump to details

----
url: https://angular.dev/api/router/PartialMatchRouteSnapshot
----

```
    
      type PartialMatchRouteSnapshot = Pick<  ActivatedRouteSnapshot,  | 'routeConfig'  | 'url'  | 'params'  | 'queryParams'  | 'fragment'  | 'data'  | 'outlet'  | 'title'  | 'paramMap'  | 'queryParamMap'>
    
    
```

Jump to details

----
url: https://angular.dev/api/core/APP_ID
----

```
    
      const APP_ID: InjectionToken<string>;
    
    
```

## [Description](#description)

A DI token representing a string ID, used primarily for prefixing application attributes and CSS styles when [`ViewEncapsulation#Emulated`](/api/core/ViewEncapsulation#Emulated) is being used.

The token is needed in cases when multiple applications are bootstrapped on a page (for example, using `bootstrapApplication` calls). In this case, ensure that those applications have different [`APP_ID`](/api/core/APP_ID) value setup. For example:

```
bootstrapApplication(ComponentA, {
  providers: [
    { provide: APP_ID, useValue: 'app-a' },
    // ... other providers ...
  ]
});

bootstrapApplication(ComponentB, {
  providers: [
    { provide: APP_ID, useValue: 'app-b' },
    // ... other providers ...
  ]
});
```

By default, when there is only one application bootstrapped, you don't need to provide the [`APP_ID`](/api/core/APP_ID) token (the `ng` will be used as an app ID).

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/transferArrayItem
----

```
function transferArrayItem<T = any>(
  currentArray: T[],
  targetArray: T[],
  currentIndex: number,
  targetIndex: number,
): void;
```

@paramcurrentArray`T[]`

Array from which to transfer the item.

@paramtargetArray`T[]`

Array into which to put the item.

@paramcurrentIndex`number`

Index of the item in its current array.

@paramtargetIndex`number`

Index at which to insert the item.

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/PreloadAllModules
----

```
    
      class PreloadAllModules implements PreloadingStrategy {}
    
    
```

@paramroute`Route`

@paramfn`() => Observable<any>`

@returns`Observable<any>`

## [Description](#description)

Provides a preloading strategy that preloads all modules as quickly as possible.

```
RouterModule.forRoot(ROUTES, {preloadingStrategy: PreloadAllModules})
```

```
export const appConfig: ApplicationConfig = {
providers: [
  provideRouter(
    routes,
    withPreloading(PreloadAllModules)
  )
]
};
```

Jump to details

----
url: https://angular.dev/cli/completion/script
----

```
ng completion script
```

Generate a bash and zsh real-time type-ahead autocompletion script.

Jump to details

----
url: https://angular.dev/guide/templates/class-binding
----

In Angular, a **binding** creates a dynamic connection between a component's template and its data. This connection ensures that changes to the component's data automatically update the rendered template.

## [Render dynamic text with text interpolation](#render-dynamic-text-with-text-interpolation)

You can bind dynamic text in templates with double curly braces, which tells Angular that it is responsible for the expression inside and ensuring it is updated correctly. This is called **text interpolation**.

```
@Component({
  template: `
    <p>Your color preference is {{ theme }}.</p>
  `,
  ...
})
export class App {
  theme = 'dark';
}
```

In this example, when the snippet is rendered to the page, Angular will replace `{{ theme }}` with `dark`.

```
<!-- Rendered Output -->
<p>Your color preference is dark.</p>
```

Bindings that change over time should read values from [signals](/guide/signals). Angular tracks the signals read in the template, and updates the rendered page when those signal values change.

```
@Component({
  template: `
    <!-- Does not necessarily update when `welcomeMessage` changes. -->
    <p>{{ welcomeMessage }}</p>

    <p>Your color preference is {{ theme() }}.</p> <!-- Always updates when the value of the `theme` signal changes. -->
  `
  ...
})
export class App {
  welcomeMessage = "Welcome, enjoy this app that we built for you";
  theme = signal('dark');
}
```

For more details, see the [Signals guide](/guide/signals).

Continuing the theme example, if a user clicks on a button that updates the `theme` signal to `'light'` after the page loads, the page updates accordingly to:

```
<!-- Rendered Output -->
<p>Your color preference is light.</p>
```

```
<!-- Bind the `disabled` property on the button element's DOM object -->
<button [disabled]="isFormValid()">Save</button>
```

In this example, every time `isFormValid` changes, Angular automatically sets the `disabled` property of the `HTMLButtonElement` instance.

### [Component and directive properties](#component-and-directive-properties)

When an element is an Angular component, you can use property bindings to set component input properties using the same square bracket syntax.

```
<!-- Bind the `value` property on the `MyListbox` component instance. -->
<my-listbox [value]="mySelection()" />
```

In this example, every time `mySelection` changes, Angular automatically sets the `value` property of the `MyListbox` instance.

You can bind to directive properties as well.

```
<!-- Bind to the `ngSrc` property of the `NgOptimizedImage` directive  -->
<img [ngSrc]="profilePhotoUrl()" alt="The current user's profile photo" />
```

### [Attributes](#attributes)

When you need to set HTML attributes that do not have corresponding DOM properties, such as SVG attributes, you can bind attributes to elements in your template with the `attr.` prefix.

```
<!-- Bind the `role` attribute on the `<ul>` element to the component's `listRole` property. -->
<ul [attr.role]="listRole()">
```

In this example, every time `listRole` changes, Angular automatically sets the `role` attribute of the `<ul>` element by calling `setAttribute`.

If the value of an attribute binding is `null`, Angular removes the attribute by calling `removeAttribute`.

### [Text interpolation in properties and attributes](#text-interpolation-in-properties-and-attributes)

You can also use text interpolation syntax in properties and attributes by using the double curly brace syntax instead of square braces around the property or attribute name. When using this syntax, Angular treats the assignment as a property binding.

```
<!-- Binds a value to the `alt` property of the image element's DOM object. -->
<img src="profile-photo.jpg" alt="Profile photo of {{ firstName() }}" />
```

## [CSS class and style property bindings](#css-class-and-style-property-bindings)

Angular supports additional features for binding CSS classes and CSS style properties to elements.

### [CSS classes](#css-classes)

You can create a CSS class binding to conditionally add or remove a CSS class on an element based on whether the bound value is [truthy or falsy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy).

```
<!-- When `isExpanded` is truthy, add the `expanded` CSS class. -->
<ul [class.expanded]="isExpanded()">
```

You can also bind directly to the `class` property. Angular accepts three types of value:

| Description of `class` value                                                                                                                                      | TypeScript type       |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| A string containing one or more CSS classes separated by spaces                                                                                                   | `string`              |
| An array of CSS class strings                                                                                                                                     | `string[]`            |
| An object where each property name is a CSS class name and each corresponding value determines whether that class is applied to the element, based on truthiness. | `Record<string, any>` |

```
@Component({
  template: `
    <ul [class]="listClasses"> ... </ul>
    <section [class]="sectionClasses()"> ... </section>
    <button [class]="buttonClasses()"> ... </button>
  `,
  ...
})
export class UserProfile {
  listClasses = 'full-width outlined';
  sectionClasses = signal(['expandable', 'elevated']);
  buttonClasses = signal({
    highlighted: true,
    embiggened: false,
  });
}
```

The above example renders the following DOM:

```
<ul class="full-width outlined"> ... </ul>
<section class="expandable elevated"> ... </section>
<button class="highlighted"> ... </button>
```

Angular ignores any string values that are not valid CSS class names.

When using static CSS classes, directly binding `class`, and binding specific classes, Angular intelligently combines all of the classes in the rendered result.

```
@Component({
  template: `<ul class="list" [class]="listType()" [class.expanded]="isExpanded()"> ...`,
  ...
})
export class Listbox {
  listType = signal('box');
  isExpanded = signal(true);
}
```

In the example above, Angular renders the `ul` element with all three CSS classes.

```
<ul class="list box expanded">
```

Angular does not guarantee any specific order of CSS classes on rendered elements.

When binding `class` to an array or an object, Angular compares the previous value to the current value with the triple-equals operator (`===`). You must create a new object or array instance when you modify these values in order for Angular to apply any updates.

If an element has multiple bindings for the same CSS class, Angular resolves collisions by following its style precedence order.

**NOTE:** Class bindings do not support space-separated class names in a single key. They also don't support mutations on objects as the reference of the binding remains the same. If you need one or the other, use the [ngClass](/api/common/NgClass) directive.

### [CSS style properties](#css-style-properties)

You can also bind to CSS style properties directly on an element.

```
<!-- Set the CSS `display` property based on the `isExpanded` property. -->
<section [style.display]="isExpanded() ? 'block' : 'none'">
```

You can further specify units for CSS properties that accept units.

```
<!-- Set the CSS `height` property to a pixel value based on the `sectionHeightInPixels` property. -->
<section [style.height.px]="sectionHeightInPixels()">
```

You can also set multiple style values in one binding. Angular accepts the following types of value:

| Description of `style` value                                                                                              | TypeScript type       |
| ------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| A string containing zero or more CSS declarations, such as `"display: flex; margin: 8px"`.                                | `string`              |
| An object where each property name is a CSS property name and each corresponding value is the value of that CSS property. | `Record<string, any>` |

```
@Component({
  template: `
    <ul [style]="listStyles()"> ... </ul>
    <section [style]="sectionStyles()"> ... </section>
  `,
  ...
})
export class UserProfile {
  listStyles = signal('display: flex; padding: 8px');
  sectionStyles = signal({
    border: '1px solid black',
    'font-weight': 'bold',
  });
}
```

The above example renders the following DOM.

```
<ul style="display: flex; padding: 8px"> ... </ul>
<section style="border: 1px solid black; font-weight: bold"> ... </section>
```

When binding `style` to an object, Angular compares the previous value to the current value with the triple-equals operator (`===`). You must create a new object instance when you modify these values in order for Angular to apply any updates.

If an element has multiple bindings for the same style property, Angular resolves collisions by following its style precedence order.

## [ARIA attributes](#aria-attributes)

Angular supports binding string values to ARIA attributes.

```
<button type="button" [aria-label]="actionLabel()">
  {{ actionLabel() }}
</button>
```

Angular writes the string value to the element’s `aria-label` attribute and removes it when the bound value is `null`.

Some ARIA features expose DOM properties or directive inputs that accept structured values (such as element references). Use standard property bindings for those cases. See the [accessibility guide](/best-practices/a11y#aria-attributes-and-properties) for examples and additional guidance.

----
url: https://angular.dev/api/core/provideExperimentalWebMcpTools
----

```
function provideExperimentalWebMcpTools<
  InputSchema extends JsonSchemaForInference,
>(tools: ToolDescriptor<InputSchema>[]): EnvironmentProviders;
```

@paramtools`ToolDescriptor<InputSchema>[]`

The tools to register and execute when invoked by an AI agent.

@returns`EnvironmentProviders`

An [`EnvironmentProviders`](/api/core/EnvironmentProviders) that can be used in `bootstrapApplication` or route providers.

## [Description](#description)

Provides a list of WebMCP tools tied to the lifecycle of the associated [`Injector`](/api/core/Injector).

The tools are automatically registered when the environment is initialized and unregistered when the associated injector is destroyed.

The `tools[number].execute` function is invoked in the injection context of the associated [`Injector`](/api/core/Injector).

Jump to details

----
url: https://angular.dev/press-kit
----

The logo graphics available for download on this page are provided under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).

### [Angular logos](https://drive.google.com/drive/folders/1IgcAwLDVZUz8ycnFa7T4_H6B4V4LhYUQ?usp=sharing)

[Black and white are the default color variations and should be used in most circumstances. A gradient version of the icon and lockup is available in both static and animated formats and can be used in cases where a color icon is required.](https://drive.google.com/drive/folders/1IgcAwLDVZUz8ycnFa7T4_H6B4V4LhYUQ?usp=sharing)

[Download from Google Drive](https://drive.google.com/drive/folders/1IgcAwLDVZUz8ycnFa7T4_H6B4V4LhYUQ?usp=sharing)

### [Brand guidelines](https://drive.google.com/drive/folders/1gD5-kamfribnib6TH4-aqVZxjYaDZlCg?usp=drive_link)

[Our brand guidelines folders contain the design files, guidance and community examples for how the brand can be adapted. Read more about adapting the logo in the section below.](https://drive.google.com/drive/folders/1gD5-kamfribnib6TH4-aqVZxjYaDZlCg?usp=drive_link)

[Download from Google Drive](https://drive.google.com/drive/folders/1gD5-kamfribnib6TH4-aqVZxjYaDZlCg?usp=drive_link)

### [Angular mascot](https://drive.google.com/drive/folders/1uX4H3exWWpwPqh52Bxig3GLS736gBEAf?usp=drive_link)

[Angie is the official mascot of the Angular framework. The mascot folders contain different file formats and poses of our friendly companion for use in your community assets and presentations.](https://drive.google.com/drive/folders/1uX4H3exWWpwPqh52Bxig3GLS736gBEAf?usp=drive_link)

[Download from Google Drive](https://drive.google.com/drive/folders/1uX4H3exWWpwPqh52Bxig3GLS736gBEAf?usp=drive_link)

## [Press and Media](#press-and-media)

For inquiries regarding press and media please contact us at <press@angular.io>. For developer inquiries contact <devrel@angular.io>.

## [Adapting the logo](#adapting-the-logo)

Angular’s success is deeply connected to our community. We know many communities, meetups, conferences, blogs, websites, YouTube channels and developers have built their brand on top of ours. This logo is for us and our community, and it’s important that you can join us in updating your brand as well.

Angular has long valued creative expression through customized shield icons. The new Angular icon allows customization in several ways. The icon shape can be used as a container for simple colors and patterns. Alternatively, a custom backplate can be used for more intricate designs. We recommend keeping your compositions simple and clear, since busy and complex patterns can be difficult to read. We appreciate your cooperation in keeping our community a safe and welcoming space. Please keep designs respectful and be mindful of our community guidelines when creating your icons.

We have provided a template to get you started.

If you have any questions on adapting the new logo, or updating your own, please email <devrel@angular.io> for additional help – we can’t wait to see what you create!

1. ### [Adapt the logo colors as your own](#adapt-the-logo-colors-as-your-own)

   Lean into the shape of Angular’s new logo by changing the logo colors to match your brand colors, flag, cause, etc.

   In this example, we’ve adapted the colors to create an Angular Pride logo variation:

2. ### [Adapt the logo shape as your own](#adapt-the-logo-shape-as-your-own)

   Lean into the shape of Angular’s new logo by adapting the shield to match your own brand.

   In this example, we’ve adapted the shield to create an Angular Signals logo variation:

3. ### [Do’s and don’ts of using Angular's brand](#dos-and-donts-of-using-angulars-brand)

   If you are creating your own logo, we encourage differentiating your logo from ours to not cause confusion. When adapting the logo, you are free to change and adapt the colors and shape to make it your own.

   When representing Angular with the original logo, please follow these guidelines:

## [Deprecated logos](#deprecated-logos)

### [2016 Angular (v3-v16)](#2016-angular-v3-v16)

In 2023, we announced a modernized logo with v17. We advise against using the former Angular logo to prevent confusion. You can view the old press kit on our [old docs site](https://angular.io/presskit).

### [AngularJS](#angularjs)

AngularJS is the v1.x predecessor of modern Angular. We advise against using this logo to prevent confusion. Read more about the discontinued LTS (Long Term Support) for AngularJS on our [blog](https://blog.angular.dev/discontinued-long-term-support-for-angularjs-cc066b82e65a).

----
url: https://angular.dev/api/cdk/testing/ModifierKeys
----

```
    
      interface ModifierKeys {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/Predicate
----

```
    
      type Predicate<T> = (value: T) => boolean
    
    
```

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/outputFromObservable
----

```
    
      
    
    
```

@paramobservable`Observable<T>`

@paramopts`OutputOptions | undefined`

@returns`OutputRef<T>`

Usage notes

Initialize an output in your directive by declaring a class field and initializing it with the [`outputFromObservable()`](/api/core/rxjs-interop/outputFromObservable) function.

```
@Directive({..})
export class MyDir {
  nameChange$ = <some-observable>;
  nameChange = outputFromObservable(this.nameChange$);
}
```

## [Usage Notes](#usage-notes)

Initialize an output in your directive by declaring a class field and initializing it with the [`outputFromObservable()`](/api/core/rxjs-interop/outputFromObservable) function.

```
@Directive({..})
export class MyDir {
  nameChange$ = <some-observable>;
  nameChange = outputFromObservable(this.nameChange$);
}
```

Jump to details

----
url: https://angular.dev/errors/NG0201
----

[](https://www.youtube.com/watch?v=lAlOryf1-WU\&autoplay=1)

You see this error when you try to inject a service but have not declared a corresponding provider. A provider is a mapping that supplies a value that you can inject into the constructor of a class in your application.

Read more on providers in our [Dependency Injection guide](guide/di).

## [Debugging the error](#debugging-the-error)

Work backwards from the object where the error states that a provider is missing: `No provider for <token> found`. This is commonly thrown in services, which require non-existing providers.

To fix the error ensure that your service is registered in the list of providers of an [`NgModule`](/api/core/NgModule) or has the [`@Service`](/api/core/Service) decorator at top.

The most common solution is to add a provider in [`@Service`](/api/core/Service)

```
@Service()
```

----
url: https://angular.dev/guide/http
----

Most front-end applications need to communicate with a server over the HTTP protocol to download or upload data and access other back-end services. Angular provides a client HTTP API for Angular applications, the [`HttpClient`](/api/common/http/HttpClient) service class in `@angular/common/http`.

## [HTTP client service features](#http-client-service-features)

The HTTP client service offers the following major features:

* The ability to request [typed response values](guide/http/making-requests#fetching-json-data)
* Streamlined [error handling](guide/http/making-requests#handling-request-failure)
* Request and response [interception](guide/http/interceptors)
* Robust [testing utilities](guide/http/testing)

## [What's next](#whats-next)

----
url: https://angular.dev/api/cdk/testing/selenium-webdriver/SeleniumWebDriverElement
----

```
    
      class SeleniumWebDriverElement implements TestElement {
}
    
    
```

@paramelement`() => webdriver.WebElement`

@param\_stabilize`() => Promise<void>`

@returns`SeleniumWebDriverElement`

@paramnewValue`string`

@returns`Promise<void>`

Selects the options at the specified indexes inside of a native `select` element.

@paramoptionIndexes`number[]`

@returns`Promise<void>`

Checks whether this element matches the given selector.

@paramselector`string`

@returns`Promise<boolean>`

Checks whether the element is focused.

@returns`Promise<boolean>`

Dispatches an event with a particular name.

@paramname`string`

Name of the event to be dispatched.

@paramdata`Record<string, EventData> | undefined`

@returns`Promise<void>`

Jump to details

----
url: https://angular.dev/api/core/@if
----

The `@if` block conditionally displays its content when its condition expression is truthy.

## [Syntax](#syntax)

```
@if (a > b) {
  {{a}} is greater than {{b}}
} @else if (b > a) {
  {{a}} is less than {{b}}
} @else {
  {{a}} is equal to {{b}}
}
```

## [Description](#description)

Content is added and removed from the DOM based on the evaluation of conditional expressions in the `@if` and `@else` blocks.

The built-in `@if` supports referencing of expression results to keep a solution for common coding patterns:

```
@if (users$ | async; as users) {
  {{ users.length }}
}
```

Jump to details

----
url: https://angular.dev/errors/NG0956
----

The identity track expression specified in the `@for` loop caused re-creation of the DOM corresponding to *all* items. This is a very expensive operation that commonly occurs when working with immutable data structures. For example:

```
@Component({
  template: `
    <button (click)="toggleAllDone()">All done!</button>
    <ul>
      @for (todo of todos; track todo) {
        <li>{{ todo.task }}</li>
      }
    </ul>
  `,
})
export class App {
  todos = [
    {id: 0, task: 'understand trackBy', done: false},
    {id: 1, task: 'use proper tracking expression', done: false},
  ];

  toggleAllDone() {
    this.todos = this.todos.map((todo) => ({...todo, done: true}));
  }
}
```

In the provided example, the entire list with all the views (DOM nodes, Angular directives, components, queries, etc.) are re-created (!) after toggling the "done" status of items. Here, a relatively inexpensive binding update to the `done` property would suffice.

Apart from having a high performance penalty, re-creating the DOM tree results in loss of state in the DOM elements (ex.: focus, text selection, sites loaded in an iframe, etc.).

## [Fixing the error](/errors/NG0956#fixing-the-error)

Change the tracking expression such that it uniquely identifies an item in a collection, regardless of its object identity. In the discussed example, the correct track expression would use the unique `id` property (`item.id`):

```
@Component({
  template: `
    <button (click)="toggleAllDone()">All done!</button>
    <ul>
      @for (todo of todos; track todo.id) {
        <li>{{ todo.task }}</li>
      }
    </ul>
  `,
})
export class App {
  todos = [
    {id: 0, task: 'understand trackBy', done: false},
    {id: 1, task: 'use proper tracking expression', done: false},
  ];

  toggleAllDone() {
    this.todos = this.todos.map((todo) => ({...todo, done: true}));
  }
}
```

----
url: https://angular.dev/api/animations/AnimationStaggerMetadata
----

```
    
      interface AnimationStaggerMetadata extends AnimationMetadata {}
    
    
```

The timing data for the steps.

One or more animation steps.

Jump to details

----
url: https://angular.dev/api/core/getNgModuleById
----

```
function getNgModuleById<T>(id: string): Type<T>;
```

@paramid`string`

@returns`Type<T>`

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/provideAnimations
----

```
function provideAnimations(): Provider[];
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@returns`Provider[]`

## [Usage Notes](#usage-notes)

The function is useful when you want to enable animations in an application bootstrapped using the `bootstrapApplication` function. In this scenario there is no need to import the [`BrowserAnimationsModule`](/api/platform-browser/animations/BrowserAnimationsModule) NgModule at all, just add providers returned by this function to the `providers` list as show below.

```
bootstrapApplication(RootComponent, {
  providers: [
    provideAnimations()
  ]
});
```

Jump to details

----
url: https://angular.dev/tools/libraries/angular-package-format
----

This document describes the Angular Package Format (APF). APF is an Angular specific specification for the structure and format of npm packages that is used by all first-party Angular packages (`@angular/core`, `@angular/material`, etc.) and most third-party Angular libraries.

APF enables a package to work seamlessly under most common scenarios that use Angular. Packages that use APF are compatible with the tooling offered by the Angular team as well as wider JavaScript ecosystem. It is recommended that third-party library developers follow the same npm package format.

**HELPFUL:** APF is versioned along with the rest of Angular, and every major version improves the package format. You can find the versions of the specification prior to v13 in this [google doc](https://docs.google.com/document/d/1CZC2rcpxffTDfRDs6p1cfbmKNLA6x5O-NtkJglDaBVs/preview).

## [Why specify a package format?](#why-specify-a-package-format)

In today's JavaScript landscape, developers consume packages in many different ways, using many different toolchains (webpack, Rollup, Rolldown, esbuild, etc.). These tools may understand and require different inputs - some tools may be able to process the latest ES language version, while others may benefit from directly consuming an older ES version.

The Angular distribution format supports all of the commonly used development tools and workflows, and adds emphasis on optimizations that result either in smaller application payload size or faster development iteration cycle (build time).

Developers can rely on Angular CLI and [ng-packagr](https://github.com/ng-packagr/ng-packagr) (a build tool Angular CLI uses) to produce packages in the Angular package format. See the [Creating Libraries](tools/libraries/creating-libraries) guide for more details.

## [File layout](#file-layout)

The following example shows a simplified version of the `@angular/core` package's file layout, with an explanation for each file in the package.

```
node_modules/@angular/core
├── README.md
├── package.json
├── fesm2022
│ ├── core.mjs
│ ├── core.mjs.map
│ ├── testing.mjs
│ └── testing.mjs.map
└── types
│ ├── core.d.ts
│ ├── testing.d.ts
```

This table describes the file layout under `node_modules/@angular/core` annotated to describe the purpose of files and directories:

| Files                                                                                 | Purpose                                                                                                                                                                                                        |
| :------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `README.md`                                                                           | Package README, used by npmjs web UI.                                                                                                                                                                          |
| `package.json`                                                                        | Primary `package.json`, describing the package itself as well as all available entrypoints and code formats. This file contains the "exports" mapping used by runtimes and tools to perform module resolution. |
| `fesm2022/`   ─ `core.mjs`   ─ `core.mjs.map`   ─ `testing.mjs`   ─ `testing.mjs.map` | Code for all entrypoints in flattened (FESM) ES2022 format, along with source maps.                                                                                                                            |
| `types/`   ─ `core.d.ts`   ─ `testing.d.ts`                                           | Bundled TypeScript type definitions for all public entrypoints.                                                                                                                                                |

### [`package.json`](#packagejson)

The primary `package.json` contains important package metadata, including the following:

* It [declares](#esm-declaration) the package to be in EcmaScript Module (ESM) format

* It contains an `"exports"` field which defines the available source code formats of all entrypoints

* It contains [keys](#legacy-resolution-keys) which define the available source code formats of the primary `@angular/core` entrypoint, for tools which do not understand `"exports"`. These keys are considered deprecated, and could be removed as the support for `"exports"` rolls out across the ecosystem.

* It declares whether the package contains [side effects](#side-effects)

### [ESM declaration](#esm-declaration)

The top-level `package.json` contains the key:

```
{
  "type": "module"
}
```

This informs resolvers that code within the package is using EcmaScript Modules as opposed to CommonJS modules.

### [`"exports"`](#exports)

The `"exports"` field has the following structure:

```
"exports": {
  "./schematics/*": {
    "default": "./schematics/*.js"
  },
  "./package.json": {
    "default": "./package.json"
  },
  ".": {
    "types": "./types/core.d.ts",
    "default": "./fesm2022/core.mjs"
  },
  "./testing": {
    "types": "./types/testing.d.ts",
    "default": "./fesm2022/testing.mjs"
  }
}
```

Of primary interest are the `"."` and the `"./testing"` keys, which define the available code formats for the `@angular/core` primary entrypoint and the `@angular/core/testing` secondary entrypoint, respectively. For each entrypoint, the available formats are:

| Formats                 | Details                                                                 |
| :---------------------- | :---------------------------------------------------------------------- |
| Typings (`.d.ts` files) | `.d.ts` files are used by TypeScript when depending on a given package. |
| `default`               | ES2022 code flattened into a single source.                             |

Tooling that is aware of these keys may preferentially select a desirable code format from `"exports"`.

Libraries may want to expose additional static files which are not captured by the exports of the JavaScript-based entry-points such as Sass mixins or pre-compiled CSS.

For more information, see [Managing assets in a library](tools/libraries/creating-libraries#managing-assets-in-a-library).

### [Legacy resolution keys](#legacy-resolution-keys)

In addition to `"exports"`, the top-level `package.json` also defines legacy module resolution keys for resolvers that don't support `"exports"`. For `@angular/core` these are:

```
{
  "module": "./fesm2022/core.mjs",
  "typings": "./types/core.d.ts",
}
```

As shown in the preceding code snippet, a module resolver can use these keys to load a specific code format.

### [Side effects](#side-effects)

The last function of `package.json` is to declare whether the package has [side effects](#sideeffects-flag).

```
{
  "sideEffects": false
}
```

Most Angular packages should not depend on top-level side effects, and thus should include this declaration.

### [Entrypoints and code splitting](#entrypoints-and-code-splitting)

Packages in the Angular Package Format contain one primary entrypoint and zero or more secondary entrypoints (for example, `@angular/common/http`). Entrypoints serve several functions.

1. They define the module specifiers from which users import code (for example, `@angular/core` and `@angular/core/testing`).

   Users typically perceive these entrypoints as distinct groups of symbols, with different purposes or capability.

   Specific entrypoints might only be used for special purposes, such as testing. Such APIs can be separated out from the primary entrypoint to reduce the chance of them being used accidentally or incorrectly.

2. They define the granularity at which code can be lazily loaded.

   Many modern build tools are only capable of "code splitting" (aka lazy loading) at the ES Module level. The Angular Package Format uses primarily a single "flat" ES Module per entry point. This means that most build tooling is not able to split code with a single entry point into multiple output chunks.

The general rule for APF packages is to use entrypoints for the smallest sets of logically connected code possible. For example, the Angular Material package publishes each logical component or set of components as a separate entrypoint - one for Button, one for Tabs, etc. This allows each Material component to be lazily loaded separately, if desired.

Not all libraries require such granularity. Most libraries with a single logical purpose should be published as a single entrypoint. `@angular/core` for example uses a single entrypoint for the runtime, because the Angular runtime is generally used as a single entity.

#### [Resolution of secondary entry points](#resolution-of-secondary-entry-points)

Secondary entrypoints can be resolved via the `"exports"` field of the `package.json` for the package.

To learn how to configure secondary entry points in a library, see [Entry points](tools/libraries/creating-libraries#entry-points).

### [README.md](#readmemd)

The README file in the Markdown format that is used to display description of a package on npm and GitHub.

Example README content of @angular/core package:

```
Angular &equals;&equals;&equals;&equals;&equals;&equals;&equals; The sources for this package are in
the main [Angular](https://github.com/angular/angular) repo.Please file issues and pull requests
against that repo. License: MIT
```

## [Library Compilation and Package Optimizations](#library-compilation-and-package-optimizations)

This section describes the compilation process for libraries and optimizations that apply to the npm package before publishing the artifact to a registry.

### [Partial compilation](#partial-compilation)

Libraries in the Angular Package Format must be published in "partial compilation" mode. This is a compilation mode for `ngc` which produces compiled Angular code that is not tied to a specific Angular runtime version, in contrast to the full compilation used for applications, where the Angular compiler and runtime versions must match exactly.

To partially compile Angular code, use the `compilationMode` flag in the `angularCompilerOptions` property of your `tsconfig.json`:

```
{
  …
  "angularCompilerOptions": {
    "compilationMode": "partial",
  }
}
```

Partially compiled library code is then converted to fully compiled code during the application build process by the Angular CLI.

If your build pipeline does not use the Angular CLI then refer to the [Consuming partial ivy code outside the Angular CLI](tools/libraries/creating-libraries#consuming-partial-ivy-code-outside-the-angular-cli) guide.

### [Flattening of ES modules](#flattening-of-es-modules)

The Angular Package Format specifies that code be published in "flattened" ES module format. This significantly reduces the build time of Angular applications as well as download and parse time of the final application bundle. Please check out the excellent post ["The cost of small modules"](https://nolanlawson.com/2016/08/15/the-cost-of-small-modules) by Nolan Lawson.

The Angular compiler can generate index ES module files. Tools like Rollup / Rolldown can use these files to generate flattened modules in a *Flattened ES Module* (FESM) file format.

FESM is a file format created by flattening all ES Modules accessible from an entrypoint into a single ES Module. It's formed by following all imports from a package and copying that code into a single file while preserving all public ES exports and removing all private imports. However, in some cases, the FESM may depend on shared chunks that are shared between multiple entry points.

The abbreviated name, FESM, pronounced *phe-som*, can be followed by a number such as FESM2020. The number refers to the language level of the JavaScript inside the module. Accordingly a FESM2022 file would be ESM+ES2022 and include import/export statements and ES2022 source code.

To generate a flattened ES Module index file, use the following configuration options in your tsconfig.json file:

```
{
  "compilerOptions": {
    …
    "module": "esnext",
    "target": "es2022",
    …
  },
  "angularCompilerOptions": {
    …
    "flatModuleOutFile": "my-ui-lib.js",
    "flatModuleId": "my-ui-lib"
  }
}
```

Once the index file (for example, `my-ui-lib.js`) is generated by ngc, bundlers and optimizers like Rollup and Rolldown can be used to produce the flattened ESM file.

### ["sideEffects" flag](#sideeffects-flag)

By default, EcmaScript Modules are side-effectful: importing from a module ensures that any code at the top level of that module should run. This is often undesirable, as most side-effectful code in typical modules is not truly side-effectful, but instead only affects specific symbols. If those symbols are not imported and used, it's often desirable to remove them in an optimization process known as tree-shaking, and the side-effectful code can prevent this.

Build tools such as webpack support a flag which allows packages to declare that they do not depend on side-effectful code at the top level of their modules, giving the tools more freedom to tree-shake code from the package. The end result of these optimizations should be smaller bundle size and better code distribution in bundle chunks after code-splitting. This optimization can break your code if it contains non-local side-effects - this is however not common in Angular applications and it's usually a sign of bad design. The recommendation is for all packages to claim the side-effect free status by setting the `sideEffects` property to `false`, and that developers follow the [Angular Style Guide](/style-guide) which naturally results in code without non-local side-effects.

More info: [webpack docs on side effects](https://github.com/webpack/webpack/tree/main/examples/side-effects)

### [ES2022 language level](#es2022-language-level)

ES2022 Language level is now the default language level that is consumed by Angular CLI and other tooling. The Angular CLI down-levels the bundle to a language level that is supported by all targeted browsers at application build time.

### [Flattening of Type Definitions (`.d.ts` bundling)](#flattening-of-type-definitions-dts-bundling)

As of APF v8, it is recommended to bundle TypeScript definitions. Bundling of type definitions can significantly speed up compilations for users, especially if there are many individual `.ts` source files in your library.

Angular uses [`rolldown-plugin-dts`](https://github.com/sxzz/rolldown-plugin-dts) to flatten `.d.ts` files (using `rolldown`, similar to how FESM files are created).

Using rolldown for `.d.ts` bundling is beneficial as it supports code splitting between entry-points. For example, consider you have multiple entrypoints relying on the same shared type, a shared `.d.ts` file would be created along with the larger flattened `.d.ts` files. This is desirable and avoids duplication of types.

### [Tslib](#tslib)

As of APF v10, it is recommended to add tslib as a direct dependency of your primary entry-point. This is because the tslib version is tied to the TypeScript version used to compile your library.

## [Examples](#examples)

The npm packages of the Angular framework are a good reference point for APF-compliant library packages. Browse the file layout and npm package on UNPKG:

## [Definition of terms](#definition-of-terms)

The following terms are used throughout this document intentionally. In this section are the definitions of all of them to provide additional clarity.

### [Package](#package)

The smallest set of files that are published to npm and installed together, for example `@angular/core`. This package includes a manifest called package.json, compiled source code, typescript definition files, source maps, metadata, etc. The package is installed with `npm install @angular/core`.

### [Symbol](#symbol)

A class, function, constant, or variable contained in a module and optionally made visible to the external world via a module export.

### [Module](#module)

Short for ECMAScript Modules. A file containing statements that import and export symbols. This is identical to the definition of modules in the ECMAScript spec.

### [ESM](#esm)

Short for ECMAScript Modules (see above).

### [FESM](#fesm)

Short for Flattened ES Modules and consists of a file format created by flattening all ES Modules accessible from an entry point into a single ES Module. Note that FESM is typically a single file, but it can depend on a shared chunk that is shared with other FESMs.

### [Module ID](#module-id)

The identifier of a module used in the import statements (for example, `@angular/core`). The ID often maps directly to a path on the filesystem, but this is not always the case due to various module resolution strategies.

### [Module specifier](#module-specifier)

A module identifier (see above).

### [Module resolution strategy](#module-resolution-strategy)

Algorithm used to convert Module IDs to paths on the filesystem. Node.js has one that is well specified and widely used, TypeScript supports several module resolution strategies, [Closure Compiler](https://developers.google.com/closure/compiler) has yet another strategy.

### [Module format](#module-format)

Specification of the module syntax that covers at minimum the syntax for the importing and exporting from a file. Common module formats are CommonJS (CJS, typically used for Node.js applications) or ECMAScript Modules (ESM). The module format indicates only the packaging of the individual modules, but not the JavaScript language features used to make up the module content. Because of this, the Angular team often uses the language level specifier as a suffix to the module format, (for example, ESM+ES2022 specifies that the module is in ESM format and contains ES2022 code).

### [Bundle](#bundle)

An artifact in the form of a single JS file, produced by a build tool (for example, [webpack](https://webpack.js.org) or [Rollup](https://rollupjs.org)) that contains symbols originating in one or more modules. Bundles are a browser-specific workaround that reduce network strain that would be caused if browsers were to start downloading hundreds if not tens of thousands of files. Node.js typically doesn't use bundles. Common bundle formats are UMD and System.register.

### [Language level](#language-level)

The language of the code (ES2022). Independent of the module format.

### [Entry point](#entry-point)

A module intended to be imported by the user. It is referenced by a unique module ID and exports the public API referenced by that module ID. An example is `@angular/core` or `@angular/core/testing`. Both entry points exist in the `@angular/core` package, but they export different symbols. A package can have many entry points.

### [Deep import](#deep-import)

A process of retrieving symbols from modules that are not Entry Points. These module IDs are usually considered to be private APIs that can change over the lifetime of the project or while the bundle for the given package is being created.

### [Top-Level import](#top-level-import)

An import coming from an entry point. The available top-level imports are what define the public API and are exposed in "@angular/name" modules, such as `@angular/core` or `@angular/common`.

### [Tree-shaking](#tree-shaking)

The process of identifying and removing code not used by an application - also known as dead code elimination. This is a global optimization performed at the application level using tools like [Rollup](https://rollupjs.org), [Rolldown](https://rolldown.rs/), [Closure Compiler](https://developers.google.com/closure/compiler), or [Terser](https://github.com/terser/terser).

### [AOT compiler](#aot-compiler)

The Ahead of Time Compiler for Angular.

### [Flattened type definitions](#flattened-type-definitions)

The bundled TypeScript definitions generated using tools like [API Extractor](https://api-extractor.com) or [rollup-plugin-dts](https://github.com/Swatinem/rollup-plugin-dts).

----
url: https://angular.dev/api/aria/combobox/ComboboxWidget
----

```
    
      class ComboboxWidget implements OnInit ,OnDestroy {}
    
    
```

A reference to the popup widget element.

The ID of the popup widget.

The ID of the active descendant in the widget.

@returns`void`

@returns`void`

Handles focus in events for the widget.

@returns`void`

Handles focus out events for the widget.

@paramevent`FocusEvent`

@returns`void`

## [Description](#description)

Identifies an element as a widget within a combobox popup.

This directive should be applied to the element that contains the options or content of the popup. It handles the communication of ID and active descendant information to the combobox.

Jump to details

----
url: https://angular.dev/guide/templates/ng-template
----

Inspired by the [native `<template>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template), the `<ng-template>` element lets you declare a **template fragment** – a section of content that you can dynamically or programmatically render.

## [Creating a template fragment](#creating-a-template-fragment)

You can create a template fragment inside of any component template with the `<ng-template>` element:

```
<p>This is a normal element</p>

<ng-template>
  <p>This is a template fragment</p>
</ng-template>
```

When the above is rendered, the content of the `<ng-template>` element is not rendered on the page. Instead, you can get a reference to the template fragment and write code to dynamically render it.

### [Binding context for fragments](#binding-context-for-fragments)

Template fragments may contain bindings with dynamic expressions:

```
@Component({
  /* ... */,
  template: `<ng-template>You've selected {{count}} items.</ng-template>`,
})
export class ItemCounter {
  count: number = 0;
}
```

Expressions or statements in a template fragment are evaluated against the component in which the fragment is declared, regardless of where the fragment is rendered.

## [Getting a reference to a template fragment](#getting-a-reference-to-a-template-fragment)

You can get a reference to a template fragment in one of three ways:

* By declaring a [template reference variable](/guide/templates/variables#template-reference-variables) on the `<ng-template>` element
* By querying for the fragment with [a component or directive query](/guide/components/queries)
* By injecting the fragment in a directive that's applied directly to an `<ng-template>` element.

In all three cases, the fragment is represented by a [TemplateRef](/api/core/TemplateRef) object.

### [Referencing a template fragment with a template reference variable](#referencing-a-template-fragment-with-a-template-reference-variable)

You can add a template reference variable to an `<ng-template>` element to reference that template fragment in other parts of the same template file:

```
<p>This is a normal element</p>

<ng-template #myFragment>
  <p>This is a template fragment</p>
</ng-template>
```

You can then reference this fragment anywhere else in the template via the `myFragment` variable.

### [Referencing a template fragment with queries](#referencing-a-template-fragment-with-queries)

You can get a reference to a template fragment using any [component or directive query API](/guide/components/queries).

You can query the [`TemplateRef`](/api/core/TemplateRef) object directly using a [`viewChild`](/api/core/viewChild) query.

```
@Component({
  /* ... */,
  template: `
    <p>This is a normal element</p>

    <ng-template>
      <p>This is a template fragment</p>
    </ng-template>
  `,
})
export class ComponentWithFragment {
  templateRef = viewChild<TemplateRef<unknown>>(TemplateRef);
}
```

You can then reference this fragment in your component code or the component's template like any other class member.

If a template contains multiple fragments, you can assign a name to each fragment by adding a template reference variable to each `<ng-template>` element and querying for the fragments based on that name:

```
@Component({
  /* ... */,
  template: `
    <p>This is a normal element</p>

    <ng-template #fragmentOne>
      <p>This is one template fragment</p>
    </ng-template>

    <ng-template #fragmentTwo>
      <p>This is another template fragment</p>
    </ng-template>
  `,
})
export class ComponentWithFragment {
    fragmentOne = viewChild<TemplateRef<unknown>>('fragmentOne');
    fragmentTwo = viewChild<TemplateRef<unknown>>('fragmentTwo');
}
```

Again, you can then reference these fragments in your component code or the component's template like any other class members.

### [Injecting a template fragment](#injecting-a-template-fragment)

A directive can inject a [`TemplateRef`](/api/core/TemplateRef) if that directive is applied directly to an `<ng-template>` element:

```
@Directive({
  selector: '[myDirective]',
})
export class MyDirective {
  private fragment = inject(TemplateRef);
}
```

```
<ng-template myDirective>
  <p>This is one template fragment</p>
</ng-template>
```

You can then reference this fragment in your directive code like any other class member.

## [Rendering a template fragment](#rendering-a-template-fragment)

Once you have a reference to a template fragment's [`TemplateRef`](/api/core/TemplateRef) object, you can render a fragment in one of two ways: in your template with the [`NgTemplateOutlet`](/api/common/NgTemplateOutlet) directive or in your TypeScript code with [`ViewContainerRef`](/api/core/ViewContainerRef).

### [Using `NgTemplateOutlet`](#using-ngtemplateoutlet)

The [`NgTemplateOutlet`](/api/common/NgTemplateOutlet) directive from `@angular/common` accepts a [`TemplateRef`](/api/core/TemplateRef) and renders the fragment as a **sibling** to the element with the outlet. You should generally use [`NgTemplateOutlet`](/api/common/NgTemplateOutlet) on an [`<ng-container>` element](/guide/templates/ng-container).

First, import [`NgTemplateOutlet`](/api/common/NgTemplateOutlet):

```
import {NgTemplateOutlet} from '@angular/common';
```

The following example declares a template fragment and renders that fragment to a `<ng-container>` element with [`NgTemplateOutlet`](/api/common/NgTemplateOutlet):

```
<p>This is a normal element</p>

<ng-template #myFragment>
  <p>This is a fragment</p>
</ng-template>

<ng-container *ngTemplateOutlet="myFragment"></ng-container>
```

This example produces the following rendered DOM:

```
<p>This is a normal element</p>
<p>This is a fragment</p>
```

### [Using `ViewContainerRef`](#using-viewcontainerref)

A **view container** is a node in Angular's component tree that can contain content. Any component or directive can inject [`ViewContainerRef`](/api/core/ViewContainerRef) to get a reference to a view container corresponding to that component or directive's location in the DOM.

You can use the `createEmbeddedView` method on [`ViewContainerRef`](/api/core/ViewContainerRef) to dynamically render a template fragment. When you render a fragment with a [`ViewContainerRef`](/api/core/ViewContainerRef), Angular appends it into the DOM as the next sibling of the component or directive that injected the [`ViewContainerRef`](/api/core/ViewContainerRef).

The following example shows a component that accepts a reference to a template fragment as an input and renders that fragment into the DOM on a button click.

```
@Component({
  /* ... */,
  selector: 'component-with-fragment',
  template: `
    <h2>Component with a fragment</h2>
    <ng-template #myFragment>
      <p>This is the fragment</p>
    </ng-template>
    <my-outlet [fragment]="myFragment" />
  `,
})
export class ComponentWithFragment { }

@Component({
  /* ... */,
  selector: 'my-outlet',
  template: `<button (click)="showFragment()">Show</button>`,
})
export class MyOutlet {
  private viewContainer = inject(ViewContainerRef);
  fragment = input<TemplateRef<unknown> | undefined>();

  showFragment() {
    if (this.fragment()) {
      this.viewContainer.createEmbeddedView(this.fragment());
    }
  }
}
```

In the example above, clicking the "Show" button results in the following output:

```
<component-with-fragment>
  <h2>Component with a fragment</h2>
  <my-outlet>
    <button>Show</button>
  </my-outlet>
  <p>This is the fragment</p>
</component-with-fragment>
```

## [Passing parameters when rendering a template fragment](#passing-parameters-when-rendering-a-template-fragment)

When declaring a template fragment with `<ng-template>`, you can additionally declare parameters accepted by the fragment. When you render a fragment, you can optionally pass a `context` object corresponding to these parameters. You can use data from this context object in binding expressions and statements, in addition to referencing data from the component in which the fragment is declared.

Each parameter is written as an attribute prefixed with `let-` with a value matching a property name in the context object:

```
<ng-template let-pizzaTopping="topping">
  <p>You selected: {{ pizzaTopping }}</p>
</ng-template>
```

### [Using `NgTemplateOutlet`](#using-ngtemplateoutlet-with-parameters)

You can bind a context object to the `ngTemplateOutletContext` input:

```
<ng-template #myFragment let-pizzaTopping="topping">
  <p>You selected: {{ pizzaTopping }}</p>
</ng-template>

<ng-container [ngTemplateOutlet]="myFragment" [ngTemplateOutletContext]="{topping: 'onion'}" />
```

### [Using `ViewContainerRef`](#using-viewcontainerref-with-parameters)

You can pass a context object as the second argument to `createEmbeddedView`:

```
this.viewContainer.createEmbeddedView(this.myFragment, {topping: 'onion'});
```

## [Providing injectors to template fragments](#providing-injectors-to-template-fragments)

When you render a template fragment, its injector context comes from the **template declaration's location**, not from where it is rendered. You can override this behavior by providing a custom injector.

### [Using `NgTemplateOutlet`](#using-ngtemplateoutlet-with-injectors)

You can pass a custom [`Injector`](/api/core/Injector) to the `ngTemplateOutletInjector` input:

```
export const THEME_DATA = new InjectionToken<string>('THEME_DATA', {
  factory: () => 'light',
});

@Component({
  selector: 'themed-panel',
  template: `<div [class]="theme">...</div>`,
})
export class ThemedPanel {
  theme = inject(THEME_DATA);
}

@Component({
  selector: 'root',
  imports: [NgTemplateOutlet, ThemedPanel],
  template: `
    <ng-template #myFragment>
      <themed-panel />
    </ng-template>
    <ng-container *ngTemplateOutlet="myFragment; injector: customInjector" />
  `,
})
export class Root {
  customInjector = Injector.create({
    providers: [{provide: THEME_DATA, useValue: 'dark'}],
  });
}
```

#### [Inheriting the outlet's injector](#inheriting-the-outlets-injector)

You can set `ngTemplateOutletInjector` to the string `'outlet'` to make the embedded view inherit its injector from the outlet's location in the DOM instead of from where the template was declared.

```
<ng-template #node let-items>
  <item-component>
    @for (child of items; track $index) {
      <ng-container
        *ngTemplateOutlet="node; context: {$implicit: child.children}; injector: 'outlet'"
      />
    }
  </item-component>
</ng-template>

<ng-container *ngTemplateOutlet="node; context: {$implicit: topLevelItems}" />
```

Each recursive rendering of the `node` template inherits the injector from the surrounding `<item-component>`, allowing each nested level to access providers scoped to its parent component.

**NOTE:** This is useful for building recursive structures or any situation where the rendered template needs access to providers from the component tree at the outlet site.

### [Using `ViewContainerRef`](#using-viewcontainerref-with-injectors)

You can pass a custom injector as part of the options object in `createEmbeddedView`:

```
this.viewContainer.createEmbeddedView(this.myFragment, context, {
  injector: myCustomInjector,
});
```

## [Structural directives](#structural-directives)

A **structural directive** is any directive that:

* Injects [`TemplateRef`](/api/core/TemplateRef)
* Injects [`ViewContainerRef`](/api/core/ViewContainerRef) and programmatically renders the injected [`TemplateRef`](/api/core/TemplateRef)

Angular supports a special convenience syntax for structural directives. If you apply the directive to an element and prefix the directive's selector with an asterisk (`*`) character, Angular interprets the entire element and all of its content as a template fragment:

```
<section *myDirective>
  <p>This is a fragment</p>
</section>
```

This is equivalent to:

```
<ng-template myDirective>
  <section>
    <p>This is a fragment</p>
  </section>
</ng-template>
```

Developers typically use structural directives to conditionally render fragments or render fragments multiple times.

For more details, see [Structural Directives](/guide/directives/structural-directives).

## [Additional resources](#additional-resources)

For examples of how [`ng-template`](/api/core/ng-template) is used in other libraries, check out:

* [Tabs from Angular Material](https://material.angular.dev/components/tabs/overview) - nothing gets rendered into the DOM until the tab is activated
* [Table from Angular Material](https://material.angular.dev/components/table/overview) - allows developers to define different ways to render data

----
url: https://angular.dev/roadmap
----

# Angular Roadmap

[](https://github.com/angular/angular/edit/main/adev/src/content/reference/roadmap.md "Edit this page")

Learn how the Angular team is building momentum on the web.

As an open source project, Angular’s daily commits, PRs and momentum is all trackable on GitHub. To increase transparency into how this daily work connects to the framework’s future, our roadmap brings together the team’s current and future planned vision.

The following projects are not associated with a particular Angular version. We will release them on completion, and they will be part of a specific version based on our release schedule, following semantic versioning. For example, we release features in the next minor after completion or the next major if they include breaking changes.

Currently, Angular has three goals for the framework:

1. Improve the [AI experience for developers](/ai)
2. Improve the [Angular developer experience](#improving-the-angular-developer-experience)
3. Improve the framework’s performance

Continue reading to learn how we plan to deliver these objectives with specific project work.

## [Explore modern Angular](#explore-modern-angular)

Start developing with the latest Angular features from our roadmap. This list represents the current status of new features from our roadmap:

### [Available to experiment with](#available-to-experiment-with)

* [Web MCP](/ai/webmcp)

### [Production ready](#production-ready)

* [Signal Forms](/guide/forms/signals/overview)
* [Resource API](/guide/signals/resource)
* [httpResource](/api/common/http/httpResource)
* [Zoneless change detection](/guide/zoneless)
* [Linked Signal API](/guide/signals/linked-signal)
* [Incremental hydration](/guide/incremental-hydration)
* [Effect API](/api/core/effect)
* [Event replay with SSR](/api/platform-browser/withEventReplay)
* [Route-level render mode](/guide/ssr)

## [Improving the AI experience for Angular Developers](#improving-the-ai-experience-for-angular-developers)

### [Bringing the best of AI to Angular](#bringing-the-best-of-ai-to-angular)

### AI Powered Angular

AI continues to shape the development landscape. It has changed how we develop apps and the types of user experiences that are possible. We plan to best support the developer community in AI-assisted coding and integrating AI in their applications.

### AI Development

The team will continue to develop meaningful integrations with tools like Google AI Studio, Gemini CLI and other agentic tooling such as Agentic IDEs like Antigravity. We plan to launch solutions that stay in line with the rapidly evolving industry. Some examples include agent skills, new MCP features and AI SDKs.

### Code Generation

[Based on our research](https://blog.angular.dev/beyond-the-horizon-how-angular-is-embracing-ai-for-next-gen-apps-7a7ed706e1a3), code generation for Angular is already high quality with modern LLMs. We will continue our investments in improving code generation for Angular. This means we will regularly evaluate code generation quality using current models and work to improve it through system instructions, documentation, and tactical framework changes.

### AI Powered Experiences

There is a new frontier for Angular developers to explore with new concepts such as Dynamic UI generation. We started with building Angular support for A2UI and are actively searching out more opportunities to support modern application experiences.

## [Improving the Angular developer experience](#improving-the-angular-developer-experience)

### [Developer velocity](#developer-velocity)

### Compiler

Microsoft has spent the last year porting the TypeScript compiler to Go, with the promise of a 5 - 10x speedup for typical TypeScript compilations. Angular has perhaps one of the deepest integrations with the TypeScript compiler, which will require bigger architectural changes to support new tsgo-based workflows for both the compiler and language service.

We're in the process of prototyping and exploring what this support would look like, and will deliver an Angular compiler that is compatible with tsgo and brings the performance benefits of Microsoft's native port to the Angular ecosystem.

### Enhanced Ecosystem compatibility

Developers are mixing AI generated code with manually generated code, and want to make use of popular libraries and integrate new experiences quickly. Angular wants to integrate well into that ecosystem - developers should be able to use the tools they love and mix and match frameworks according to their requirements.

As part of this project, we'll explore the requirement space of cross framework interop and our build tooling to improve our compatibility.

### Components

In Angular v21, we launched Angular Aria in developer preview, providing eight patterns for accessible, headless components. In Angular v22, we promoted Angular Aria to stable, and we plan to introduce new patterns where needed. We want to provide developers with a solid foundation for developing their own components using Angular Aria - we provide the interactions and you bring the style that matches your design systems. Developers will have the choice of developing custom components with Angular Aria, use interaction patterns from the CDK, or use ready-made styled Material Components.

For accessibility, we are continuously evaluating the components and patterns against accessibility standards such as WCAG and are working to fix any issues that arise from this process.

### [Improve tooling](#improve-tooling)

### Modernize unit testing tooling with ng test

Following the stable release of Vitest in Angular v21, it is now our primary test runner. We are now focused on promoting our experimental Karma to Vitest migration tool to stable as well as investigating new features to further refine and improve the developer testing workflow.

## [Completed projects](#completed-projects)

### [Signal Forms](/guide/forms/signals/overview)

[Signal Forms are now stable. This new approach allows developers to manage form state using signals, providing an ergonomic forms creation experience. We promoted Signal Forms to stable and enhanced interoperability with reactive forms - enabling teams to progressively migrate large forms at their own pace.](/guide/forms/signals/overview)

[Completed in 2026](/guide/forms/signals/overview)

### [Reactivity](/guide/signals)

[We introduced new signal APIs, `resource()` and `httpResource()`, for flexible asynchronous data handling. We promoted these APIs to developer stable.](/guide/signals)

[Completed in 2026](/guide/signals)

### [Change Detection](/api/core/ChangeDetectionStrategy)

[With Zoneless being stable and default, we set the default change detection strategy to `OnPush`, to follow current best practices. We also renamed `ChangeDetectionStrategy.Default` to `ChangeDetectionStrategy.Eager`.](/api/core/ChangeDetectionStrategy)

[See the RFC discussion for details.](/api/core/ChangeDetectionStrategy)

[Completed in 2026](/api/core/ChangeDetectionStrategy)

### Signal debugging in Angular DevTools

We've added better tooling for debugging Signals using Angular DevTools. This change includes a new UI for inspecting and debugging signals.

Completed in 2026

### [Improve HMR (Hot Module Reload)](https://github.com/angular/angular/issues/39367#issuecomment-1439537306)

[We're working towards faster edit/refresh cycle by enabling hot module replacement. In Angular v19 we shipped initial support for CSS and template HMR and in v20 we graduated template HMR to stable. We'll continue collecting feedback to make sure we're addressing developers' needs before we mark this project as complete.](https://github.com/angular/angular/issues/39367#issuecomment-1439537306)

[Completed in 2025](https://github.com/angular/angular/issues/39367#issuecomment-1439537306)

### Zoneless Angular

In v18 we shipped experimental zoneless support in Angular. It enables developers to use the framework without including zone.js in their bundle, which improves performance, debugging experience, and interoperability. As part of the initial release we also introduced zoneless support to the Angular CDK and Angular Material.

In v19 we introduced zoneless support in server-side rendering, addressed some edge cases, and created a schematic to scaffold zoneless projects. We transitioned [Google Fonts](https://fonts.google.com/) to zoneless which improved performance, developer experience, and allowed us to identify gaps that we need to address before moving this feature to developer preview.

As of Angular v20.2, Zoneless Angular is now stable and includes improvements in error handling and server-side rendering.

Completed in Q4 2025

### Server route configuration

We're working towards enabling a more ergonomic route configuration on the server. We want to make it trivial to declare which routes should be server-side rendered, prerendered or client-side rendered.

In Angular v19 we shipped developer preview of route-level render mode which allows you to granularly configure which routes you want Angular to prerender, server-side render or client-side render. In Angular v20 we graduated it to stable.

Completed in Q2 2025

### Enable incremental hydration

In v17 we graduated hydration from developer preview and we've been consistently observing 40-50% improvements in LCP. Since then we started prototyping incremental hydration and shared a demo on stage at ng-conf.

In v19 we shipped the incremental hydration in developer preview mode, powered by `@defer` blocks. In Angular v20 we graduated it to stable!

Completed in Q2 2025

### [Deliver Angular Signals](https://github.com/angular/angular/discussions/49685)

[This project rethinks the Angular reactivity model by introducing Signals as a reactivity primitive. The initial planning resulted in hundreds of discussions, conversations with developers, feedback sessions, user experience studies, and a series of RFCs, which received over 1,000 comments.](https://github.com/angular/angular/discussions/49685)

[In Angular v20 we graduated all the fundamental reactivity primitives to stable including signal, effect, linkedSignal, signal-based queries, and inputs.](https://github.com/angular/angular/discussions/49685)

[Completed in Q2 2025](https://github.com/angular/angular/discussions/49685)

### [Support two-dimensional drag-and-drop](https://github.com/angular/components/issues/13372)

[As part of this project, we implemented mixed orientation support for the Angular CDK drag and drop. This is one of the repository's most highly requested features.](https://github.com/angular/components/issues/13372)

[Completed in Q2 2024](https://github.com/angular/components/issues/13372)

### [Event replay with SSR and prerendering](api/platform-browser/withEventReplay)

[In v18 we introduced an event replay functionality when using server-side rendering or prerendering. For this feature we depend on the event dispatch primitive (previously known as jsaction) that is running on Google.com.](api/platform-browser/withEventReplay)

[In Angular v19 we graduated event replay to stable and enabled it by default for all new projects.](api/platform-browser/withEventReplay)

[Completed in Q4 2024](api/platform-browser/withEventReplay)

### Integrate Angular Language Service with Schematics

To make it easier for developers to use modern Angular APIs, we enabled integration between the Angular language service and schematics which allows you to refactor your app with a single click.

Completed in Q4 2024

### Streamline standalone imports with Language Service

As part of this initiative, the language service automatically imports components and pipes in standalone and NgModule-based apps. Additionally, we've added a template diagnostic to highlight unused imports in standalone components, which should help make application bundles smaller.

Completed in Q4 2024

### Local template variables

We've released the support for local template variables in Angular, see [`@let`](/api/core/@let) for additional information.

Completed in Q3 2024

### [Expand the customizability of Angular Material](https://material.angular.dev/guide/theming)

[To provide better customization of our Angular Material components and enable Material 3 capabilities, we'll be collaborating with Google's Material Design team on defining token-based theming APIs.](https://material.angular.dev/guide/theming)

[In v17.2 we shared experimental support for Angular Material 3 and in v18 we graduated it to stable.](https://material.angular.dev/guide/theming)

[Completed in Q2 2024](https://material.angular.dev/guide/theming)

### [Introduce deferred loading](guide/templates/defer)

[In v17 we shipped deferrable views in developer preview, which provide an ergonomic API for deferred code loading. In v18 we enabled deferrable views for library developers and graduated the API to stable.](guide/templates/defer)

[Completed in Q2 2024](guide/templates/defer)

### iframe support in Angular DevTools

We enabled debugging and profiling of Angular apps embedded within an iframe on the page.

Completed in Q2 2024

### [Automation for transition of existing hybrid rendering projects to esbuild and vite](tools/cli/build-system-migration)

[In v17 we shipped a vite and esbuild-based application builder and enabled it for new projects by default. It improves build time for projects using hybrid rendering with up to 87%. As part of v18 we shipped schematics and a guide that migrate existing projects using hybrid rendering to the new build pipeline.](tools/cli/build-system-migration)

[Completed in Q2 2024](tools/cli/build-system-migration)

### [Make Angular.dev the official home for Angular developers](https://goo.gle/angular-dot-dev)

[Angular.dev is the new site, domain and home for Angular development. The new site contains updated documentation, tutorials and guidance that will help developers build with Angular’s latest features.](https://goo.gle/angular-dot-dev)

[Completed in Q2 2024](https://goo.gle/angular-dot-dev)

### [Introduce built-in control flow](guide/templates/control-flow)

[In v17 we shipped a developer preview version of a new control flow. It brings significant performance improvements and better ergonomics for template authoring. We also provided a migration of existing `*ngIf`, `*ngFor`, and `*ngSwitch` which you can run to move your project to the new implementation. As of v18 the built-in control flow is now stable.](guide/templates/control-flow)

[Completed in Q2 2024](guide/templates/control-flow)

### Modernize getting started tutorial

Over the past two quarters, we developed a new [video](https://www.youtube.com/watch?v=xAT0lHYhHMY\&list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF) and [textual](/tutorials/learn-angular) tutorial based on standalone components.

Completed in Q4 2023

### [Investigate modern bundlers](guide/hydration)

[In Angular v16, we released a developer preview of an esbuild-based builder with support for `ng build` and `ng serve`. The `ng serve` development server uses Vite and a multi-file compilation by esbuild and the Angular compiler. In v17 we graduated the build tooling from developer preview and enabled it by default for new projects.](guide/hydration)

[Completed in Q4 2023](guide/hydration)

### [Introduce dependency injection debugging APIs](tools/devtools)

[To improve the debugging utilities of Angular and Angular DevTools, we'll work on APIs that provide access to the dependency injection runtime. As part of the project, we'll expose debugging methods that allow us to explore the injector hierarchy and the dependencies across their associated providers. As of v17, we shipped a feature that enables us to plug into the dependency injection life-cycle. We also launched a visualization of the injector tree and inspection of the providers declared inside each individual node,](tools/devtools)

[Completed in Q4 2023](tools/devtools)

### [Improve documentation and schematics for standalone components](essentials/components)

[We released a developer preview of the `ng new --standalone` schematics collection, allowing you to create apps free of NgModules. In v17 we switched the new application authoring format to standalone APIs and changed the documentation to reflect the recommendation. Additionally, we shipped schematics which support updating existing applications to standalone components, directives, and pipes. Even though NgModules will stick around for foreseeable future, we recommend you to explore the benefits of the new APIs to improve developer experience and benefit from the new features we build for them.](essentials/components)

[Completed in Q4 2023](essentials/components)

### Explore hydration and server-side rendering improvements

In v16, we released a developer preview of non-destructive full hydration, see the [hydration guide](guide/hydration) and the [blog post](https://blog.angular.dev/whats-next-for-server-side-rendering-in-angular-2a6f27662b67) for additional information. We're already seeing significant improvements to Core Web Vitals, including [LCP](https://web.dev/lcp) and [CLS](https://web.dev/cls). In lab tests, we consistently observed 45% better LCP of a real-world app.

In v17 we launched hydration outside developer preview and did a series of improvements in the server-side rendering story, including: route discovery at runtime for SSG, up to 87% faster build times for hybrid rendered applications, prompt that enables hybrid rendering for new projects.

Completed in Q4 2023

### [Non-destructive full app hydration](guide/hydration)

[In v16, we released a developer preview of non-destructive full hydration, which allows Angular to reuse existing DOM nodes on a server-side rendered page, instead of re-creating an app from scratch. See additional information in the hydration guide.](guide/hydration)

[Completed in Q1 2023](guide/hydration)

### [Improvements in the image directive](guide/image-optimization)

[We released the Angular image directive as stable in v15. We introduced a new fill mode feature that enables images to fit within their parent container rather than having explicit dimensions. Over the past two months, the Chrome Aurora team backported the directive to v12 and newer.](guide/image-optimization)

[Completed in Q1 2023](guide/image-optimization)

### [Documentation refactoring](https://angular.io)

[Ensure all existing documentation fits into a consistent set of content types. Update excessive use of tutorial-style documentation into independent topics. We want to ensure the content outside the main tutorials is self-sufficient without being tightly coupled to a series of guides. In Q2 2022, we refactored the template content and dependency injection. In Q1 2023, we improved the HTTP guides, and with this, we're putting the documentation refactoring project on hold.](https://angular.io)

[Completed in Q1 2023](https://angular.io)

### [Improve image performance](guide/image-optimization)

[The Aurora and the Angular teams are working on the implementation of an image directive that aims to improve Core Web Vitals. We shipped a stable version of the image directive in v15.](guide/image-optimization)

[Completed in Q4 2022](guide/image-optimization)

### [Modern CSS](https://blog.angular.dev/modern-css-in-angular-layouts-4a259dca9127)

[The Web ecosystem evolves constantly and we want to reflect the latest modern standards in Angular. In this project we aim to provide guidelines on using modern CSS features in Angular to ensure developers follow best practices for layout, styling, etc. We shared official guidelines for layout and as part of the initiative stopped publishing flex layout.](https://blog.angular.dev/modern-css-in-angular-layouts-4a259dca9127)

[Completed in Q4 2022](https://blog.angular.dev/modern-css-in-angular-layouts-4a259dca9127)

### [Support adding directives to host elements](guide/directives/directive-composition-api)

[A long-standing feature request is to add the ability to add directives to host elements. The feature lets developers augment their own components with additional behaviors without using inheritance. In v15 we shipped our directive composition API, which enables enhancing host elements with directives.](guide/directives/directive-composition-api)

[Completed in Q4 2022](guide/directives/directive-composition-api)

### [Better stack traces](https://developer.chrome.com/blog/devtools-better-angular-debugging/)

[The Angular and the Chrome DevTools are working together to enable more readable stack traces for error messages. In v15 we released improved relevant and linked stack traces. As a lower priority initiative, we'll be exploring how to make the stack traces friendlier by providing more accurate call frame names for templates.](https://developer.chrome.com/blog/devtools-better-angular-debugging/)

[Completed in Q4 2022](https://developer.chrome.com/blog/devtools-better-angular-debugging/)

### [Enhanced Angular Material components by integrating MDC Web](https://material.angular.dev/guide/mdc-migration)

[MDC Web is a library created by the Google Material Design team that provides reusable primitives for building Material Design components. The Angular team is incorporating these primitives into Angular Material. Using MDC Web aligns Angular Material more closely with the Material Design specification, expands accessibility, improves component quality, and improves the velocity of our team.](https://material.angular.dev/guide/mdc-migration)

[Completed in Q4 2022](https://material.angular.dev/guide/mdc-migration)

### [Implement APIs for optional NgModules](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

[In the process of making Angular simpler, we are working on introducing APIs that allow developers to initialize apps, instantiate components, and use the router without NgModules. Angular v14 introduces developer preview of the APIs for standalone components, directives, and pipes. In the next few quarters we'll collect feedback from developers and finalize the project making the APIs stable. As the next step we will work on improving use cases such as TestBed, Angular elements, etc.](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

[Completed in Q4 2022](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

### [Allow binding to protected fields in templates](guide/templates/binding)

[To improve the encapsulation of Angular components we enabled binding to protected members of the component instance. This way you'll no longer have to expose a field or a method as public to use it inside your templates.](guide/templates/binding)

[Completed in Q2 2022](guide/templates/binding)

### [Publish guides on advanced concepts](https://angular.io/guide/change-detection)

[Develop and publish an in-depth guide on change detection. Develop content for performance profiling of Angular apps. Cover how change detection interacts with Zone.js and explain when it gets triggered, how to profile its duration, as well as common practices for performance optimization.](https://angular.io/guide/change-detection)

[Completed in Q2 2022](https://angular.io/guide/change-detection)

### [Rollout strict typings for @angular/forms](guide/forms/typed-forms)

[In Q4 2021 we designed a solution for introducing strict typings for forms and in Q1 2022 we concluded the corresponding request for comments. Currently, we are implementing a rollout strategy with an automated migration step that will enable the improvements for existing projects. We are first testing the solution with more than 2,500 projects at Google to ensure a smooth migration path for the external community.](guide/forms/typed-forms)

[Completed in Q2 2022](guide/forms/typed-forms)

### [Remove legacy View Engine](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

[After the transition of all our internal tooling to Ivy is completed, we will remove the legacy View Engine for reduced Angular conceptual overhead, smaller package size, lower maintenance cost, and lower codebase complexity.](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

[Completed in Q1 2022](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

### [Simplified Angular mental model with optional NgModules](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

[To simplify the Angular mental model and learning journey, we will be working on making NgModules optional. This work lets developers develop standalone components and implement an alternative API for declaring the compilation scope of the component. We kicked this project off with high-level design discussions that we captured in an RFC.](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

[Completed in Q1 2022](https://blog.angular.dev/angular-v15-is-now-available-df7be7f2f4c8)

### [Design strict typing for @angular/forms](guide/forms/typed-forms)

[We will work on finding a way to implement stricter type checking for reactive forms with minimal backward incompatible implications. This way, we let developers catch more issues during development time, enable better text editor and IDE support, and improve the type checking for reactive forms.](guide/forms/typed-forms)

[Completed in Q1 2022](guide/forms/typed-forms)

### [Improve integration of Angular DevTools with framework](tools/devtools)

[To improve the integration of Angular DevTools with the framework, we are working on moving the codebase to the angular/angular monorepository. This includes transitioning Angular DevTools to Bazel and integrating it into the existing processes and CI pipeline.](tools/devtools)

[Completed in Q1 2022](tools/devtools)

### [Launch advanced compiler diagnostics](extended-diagnostics)

[Extend the diagnostics of the Angular compiler outside type checking. Introduce other correctness and conformance checks to further guarantee correctness and best practices.](extended-diagnostics)

[Completed in Q1 2022](extended-diagnostics)

### [Update our e2e testing strategy](guide/testing)

[To ensure we provide a future-proof e2e testing strategy, we want to evaluate the state of Protractor, community innovations, e2e best practices, and explore novel opportunities. As first steps of the effort, we shared an RFC and worked with partners to ensure smooth integration between the Angular CLI and state-of-the-art tooling for e2e testing. As the next step, we need to finalize the recommendations and compile a list of resources for the transition.](guide/testing)

[Completed in Q3 2021](guide/testing)

### [Angular libraries use Ivy](tools/libraries)

[Earlier in 2020, we shared an RFC for Ivy library distribution. After invaluable feedback from the community, we developed a design of the project. We are now investing in the development of Ivy library distribution, including an update of the library package format to use Ivy compilation, unblock the deprecation of the View Engine library format, and ngcc.](tools/libraries)

[Completed in Q3 2021](tools/libraries)

### [Improve test times and debugging with automatic test environment tear down](guide/testing)

[To improve test time and create better isolation across tests, we want to change TestBed to automatically clean up and tear down the test environment after each test run.](guide/testing)

[Completed in Q3 2021](guide/testing)

### [Deprecate and remove IE11 support](https://github.com/angular/angular/issues/41840)

[Internet Explorer 11 (IE11) has been preventing Angular from taking advantage of some of the modern features of the Web platform. As part of this project we are going to deprecate and remove IE11 support to open the path for modern features that evergreen browsers provide. We ran an RFC to collect feedback from the community and decide on next steps to move forward.](https://github.com/angular/angular/issues/41840)

[Completed in Q3 2021](https://github.com/angular/angular/issues/41840)

### [Leverage ES2017+ as the default output language](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)

[Supporting modern browsers lets us take advantage of the more compact, expressive, and performant new syntax of JavaScript. As part of this project we will investigate what the blockers are to moving forward with this effort, and take the steps to enable it.](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)

[Completed in Q3 2021](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)

### [Accelerated debugging and performance profiling with Angular DevTools](tools/devtools)

[We are working on development tooling for Angular that provides utilities for debugging and performance profiling. This project aims to help developers understand the component structure and the change detection in an Angular app.](tools/devtools)

[Completed in Q2 2021](tools/devtools)

### [Streamline releases with consolidated Angular versioning & branching](reference/releases)

[We want to consolidate release management tooling between the multiple GitHub repositories for Angular (angular/angular, angular/angular-cli, and angular/components). This effort lets us reuse infrastructure, unify and simplify processes, and improve the reliability of our release process.](reference/releases)

[Completed in Q2 2021](reference/releases)

### [Higher developer consistency with commit message standardization](https://github.com/angular/angular)

[We want to unify commit message requirements and conformance across Angular repositories (angular/angular, angular/components, and angular/angular-cli) to bring consistency to our development process and reuse infrastructure tooling.](https://github.com/angular/angular)

[Completed in Q2 2021](https://github.com/angular/angular)

### [Transition the Angular language service to Ivy](tools/language-service)

[The goal of this project is to improve the experience and remove legacy dependency by transitioning the language service to Ivy. Today the language service still uses the View Engine compiler and type checking, even for Ivy apps. We want to use the Ivy template parser and improved type checking for the Angular Language service to match app behavior. This migration is also a step towards unblocking the removal of View Engine, which will simplify Angular, reduce the npm package size, and improve the maintainability of the framework.](tools/language-service)

[Completed in Q2 2021](tools/language-service)

### [Increased security with native Trusted Types in Angular](best-practices/security)

[In collaboration with the Google security team, we are adding support for the new Trusted Types API. This web platform API helps developers build more secure web apps.](best-practices/security)

[Completed in Q2 2021](best-practices/security)

### [Optimized build speed and bundle sizes with Angular CLI webpack 5](tools/cli/build)

[As part of the v11 release, we introduced an opt-in preview of webpack 5 in the Angular CLI. To ensure stability, we will continue iterating on the implementation to enable build speed and bundle size improvements.](tools/cli/build)

[Completed in Q2 2021](tools/cli/build)

### [Faster apps by inlining critical styles in Universal apps](guide/ssr)

[Loading external stylesheets is a blocking operation, which means that the browser cannot start rendering your app until it loads all the referenced CSS. Having render-blocking resources in the header of a page can significantly impact its load performance, for example, its first contentful paint. To make apps faster, we have been collaborating with the Google Chrome team on inlining critical CSS and loading the rest of the styles asynchronously.](guide/ssr)

[Completed in Q1 2021](guide/ssr)

### [Improve debugging with better Angular error messages](errors)

[Error messages often bring limited actionable information to help developers resolve them. We have been working on making error messages more discoverable by adding associated codes, developing guides, and other materials to ensure a smoother debugging experience.](errors)

[Completed in Q1 2021](errors)

### [Improved developer onboarding with refreshed introductory documentation](tutorials)

[We will redefine the user learning journeys and refresh the introductory documentation. We will clearly state the benefits of Angular, how to explore its capabilities and provide guidance so developers can become proficient with the framework in as little time as possible.](tutorials)

[Completed in Q1 2021](tutorials)

### [Expand component harnesses best practices](https://material.angular.dev/guide/using-component-harnesses)

[Angular CDK introduced the concept of component test harnesses to Angular in version 9. Test harnesses let component authors create supported APIs for testing component interactions. We are continuing to improve this harness infrastructure and clarifying the best practices around using harnesses. We are also working to drive more harness adoption inside of Google.](https://material.angular.dev/guide/using-component-harnesses)

[Completed in Q1 2021](https://material.angular.dev/guide/using-component-harnesses)

### [Author a guide for content projection](https://angular.io/docs)

[Content projection is a core Angular concept that does not have the presence it deserves in the documentation. As part of this project we want to identify the core use cases and concepts for content projection and document them.](https://angular.io/docs)

[Completed in Q2 2021](https://angular.io/docs)

### [Migrate to ESLint](tools/cli)

[With the deprecation of TSLint we will be moving to ESLint. As part of the process, we will work on ensuring backward compatibility with our current recommended TSLint configuration, implement a migration strategy for existing Angular apps and introduce new tooling to the Angular CLI toolchain.](tools/cli)

[Completed in Q4 2020](tools/cli)

### [Operation Bye Bye Backlog (also known as Operation Byelog)](https://github.com/angular/angular/issues)

[We are actively investing up to 50% of our engineering capacity on triaging issues and PRs until we have a clear understanding of broader community needs. After that, we will commit up to 20% of our engineering capacity to keep up with new submissions promptly.](https://github.com/angular/angular/issues)

[Completed in Q4 2020](https://github.com/angular/angular/issues)

----
url: https://angular.dev/api/common/I18nPluralPipe
----

```
{{ value_expression | i18nPlural:pluralMap:locale }}
```

## [API](#api)

```
    
      class I18nPluralPipe implements PipeTransform {}
    
    
```

@param\_localization`NgLocalization`

@returns`I18nPluralPipe`

@paramvalue`number | null | undefined`

the number to be formatted

@parampluralMap`{ [count: string]: string; }`

an object that mimics the ICU format, see <https://unicode-org.github.io/icu/userguide/format_parse/messages/>.

@paramlocale`string | undefined`

a `string` defining the locale to use (uses the current [`LOCALE_ID`](/api/core/LOCALE_ID) by default).

@returns`string`

## [Description](#description)

Maps a value to a string that pluralizes the value according to locale rules.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

### [Example](#example)

```
@Component({
  selector: 'i18n-plural-pipe',
  imports: [I18nPluralPipe],
  template: `<div>{{ messages.length | i18nPlural: messageMapping }}</div>`,
})
export class I18nPluralPipeComponent {
  messages: any[] = ['Message 1'];
  messageMapping: {[k: string]: string} = {
    '=0': 'No messages.',
    '=1': 'One message.',
    'other': '# messages.',
  };
}
```

Jump to details

----
url: https://angular.dev/api/core/CUSTOM_ELEMENTS_SCHEMA
----

```
    
      const CUSTOM_ELEMENTS_SCHEMA: SchemaMetadata;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/MissingTranslationStrategy
----

## [API](/api/core/MissingTranslationStrategy#api)

```
    
      enum MissingTranslationStrategy {}
    
    
```

## [Description](/api/core/MissingTranslationStrategy#description)

Use this enum at bootstrap as an option of `bootstrapModule` to define the strategy that the compiler should use in case of missing translations:

* Error: throw if you have missing translations.
* Warning (default): show a warning in the console and/or shell.
* Ignore: do nothing.

See the [i18n guide](guide/i18n/merge#report-missing-translations) for more information.

Jump to details

----
url: https://angular.dev/api/animations/NoopAnimationPlayer
----

```
    
      class NoopAnimationPlayer implements AnimationPlayer {}
    
    
```

@paramduration`number`

@paramdelay`number`

@returns`NoopAnimationPlayer`

@paramfn`() => void`

@returns`void`

@paramfn`() => void`

@returns`void`

@paramfn`() => void`

@returns`void`

@returns`boolean`

@returns`void`

@returns`void`

@returns`void`

@returns`void`

@returns`void`

@returns`void`

@returns`void`

@paramposition`number`

@returns`void`

@returns`number`

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CDK_DRAG_CONFIG
----

## [API](/api/cdk/drag-drop/CDK_DRAG_CONFIG#api)

```
    
      const CDK_DRAG_CONFIG: any;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/SchemaMetadata
----

```
    
      interface SchemaMetadata {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ResourceStreamItem
----

```
    
      type ResourceStreamItem<T> = {value: T} | {error: Error}
    
    
```

Jump to details

----
url: https://angular.dev/best-practices/performance/ssr
----

Angular ships all applications as client-side rendered (CSR) by default. While this approach delivers an initial payload that's lightweight, it introduces trade-offs including slower load times, degraded performance metrics, and higher resource demands since the user's device performs most of the computations. As a result, many applications achieve significant performance improvements by integrating server-side rendering (SSR) into a hybrid rendering strategy.

## [What is hybrid rendering?](#what-is-hybrid-rendering)

Hybrid rendering allows developers to leverage the benefits of server-side rendering (SSR), pre-rendering (also known as "static site generation" or SSG) and client-side rendering (CSR) to optimize your Angular application. It gives you fine-grained control over how the different parts of your app are rendered to give your users the best experience possible.

## [Setting up hybrid rendering](#setting-up-hybrid-rendering)

You can create a **new** project with hybrid rendering by using the server-side rendering flag (i.e., `--ssr`) with the Angular CLI `ng new` command:

```
ng new --ssr
```

You can also enable hybrid rendering by adding server-side rendering to an existing project with the `ng add` command:

```
ng add @angular/ssr
```

**NOTE:** By default, Angular prerenders your entire application and generates a server file. To disable this and create a fully static app, set `outputMode` to `static`. To enable SSR, update the server routes to use [`RenderMode.Server`](/api/ssr/RenderMode#Server). For more details, see [`Server routing`](#server-routing) and [`Generate a fully static application`](#generate-a-fully-static-application).

## [Server routing](#server-routing)

### [Configuring server routes](#configuring-server-routes)

You can create a server route config by declaring an array of [`ServerRoute`](api/ssr/ServerRoute "API reference") objects. This configuration typically lives in a file named `app.routes.server.ts`.

### app.routes.server.ts

```
import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: '', // This renders the "/" route on the client (CSR)
    renderMode: RenderMode.Client,
  },
  {
    path: 'about', // This page is static, so we prerender it (SSG)
    renderMode: RenderMode.Prerender,
  },
  {
    path: 'profile', // This page requires user-specific data, so we use SSR
    renderMode: RenderMode.Server,
  },
  {
    path: '**', // All other routes will be rendered on the server (SSR)
    renderMode: RenderMode.Server,
  },
];
```

You can add this config to your application with [`provideServerRendering`](api/ssr/provideServerRendering "API reference") using the [`withRoutes`](api/ssr/withRoutes "API reference") function:

### app.config.server.ts

```
import {provideServerRendering, withRoutes} from '@angular/ssr';
import {serverRoutes} from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(withRoutes(serverRoutes)),
    // ... other providers ...
  ],
};
```

When using the [App shell pattern](ecosystem/service-workers/app-shell), you must specify the component to be used as the app shell for client-side rendered routes. To do this, use the [`withAppShell`](api/ssr/withAppShell "API reference") feature:

```
import {provideServerRendering, withRoutes, withAppShell} from '@angular/ssr';
import {AppShell} from './app-shell';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(withRoutes(serverRoutes), withAppShell(AppShell)),
    // ... other providers ...
  ],
};
```

### [Rendering modes](#rendering-modes)

The server routing configuration lets you specify how each route in your application should render by setting a [`RenderMode`](api/ssr/RenderMode "API reference"):

| Rendering mode      | Description                                                                                                 |
| ------------------- | ----------------------------------------------------------------------------------------------------------- |
| **Server (SSR)**    | Renders the application on the server for each request, sending a fully populated HTML page to the browser. |
| **Client (CSR)**    | Renders the application in the browser. This is the default Angular behavior.                               |
| **Prerender (SSG)** | Prerenders the application at build time, generating static HTML files for each route.                      |

```
import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'profile',
    renderMode: RenderMode.Server,
    headers: {
      'X-My-Custom-Header': 'some-value',
    },
    status: 201,
  },
  // ... other routes
];
```

```
import {RenderMode, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'post/:id',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      const dataService = inject(PostService);
      const ids = await dataService.getIds(); // Assuming this returns ['1', '2', '3']

      return ids.map((id) => ({id})); // Generates paths like: /post/1, /post/2, /post/3
    },
  },
  {
    path: 'post/:id/**',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      return [
        {id: '1', '**': 'foo/3'},
        {id: '2', '**': 'bar/4'},
      ]; // Generates paths like: /post/1/foo/3, /post/2/bar/4
    },
  },
];
```

```
import {RenderMode, PrerenderFallback, ServerRoute} from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  {
    path: 'post/:id',
    renderMode: RenderMode.Prerender,
    fallback: PrerenderFallback.Client, // Fallback to CSR if not prerendered
    async getPrerenderParams() {
      // This function returns an array of objects representing prerendered posts at the paths:
      // `/post/1`, `/post/2`, and `/post/3`.
      // The path `/post/4` will utilize the fallback behavior if it's requested.
      return [{id: '1'}, {id: '2'}, {id: '3'}];
    },
  },
];
```

## [Authoring server-compatible components](#authoring-server-compatible-components)

Some common browser APIs and capabilities might not be available on the server. Applications cannot make use of browser-specific global objects like `window`, `document`, `navigator`, or `location` as well as certain properties of `HTMLElement`.

In general, code which relies on browser-specific symbols should only be executed in the browser, not on the server. This can be enforced through the [`afterEveryRender`](/api/core/afterEveryRender) and [`afterNextRender`](/api/core/afterNextRender) lifecycle hooks. These are only executed on the browser and skipped on the server.

```
import {Component, ElementRef, viewChild, afterNextRender} from '@angular/core';

@Component({
  selector: 'my-cmp',
  template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
  contentRef = viewChild.required<ElementRef>('content');

  constructor() {
    afterNextRender(() => {
      // Safe to check `scrollHeight` because this will only run in the browser, not the server.
      console.log('content height: ' + this.contentRef().nativeElement.scrollHeight);
    });
  }
}
```

```
export abstract class AnalyticsService {
  abstract trackEvent(name: string): void;
}
```

Create the browser implementation:

```
@Injectable()
export class BrowserAnalyticsService implements AnalyticsService {
  trackEvent(name: string): void {
    // Sends the event to the browser-based third-party analytics provider
  }
}
```

Create the server implementation:

```
@Injectable()
export class ServerAnalyticsService implements AnalyticsService {
  trackEvent(name: string): void {
    // Records the event on the server
  }
}
```

Register the browser implementation in your main application configuration:

### app.config.ts

```
export const appConfig: ApplicationConfig = {
  providers: [{provide: AnalyticsService, useClass: BrowserAnalyticsService}],
};
```

Override with the server implementation in your server configuration:

### app.config.server.ts

```
const serverConfig: ApplicationConfig = {
  providers: [{provide: AnalyticsService, useClass: ServerAnalyticsService}],
};
```

Inject and use the service in your components:

```
@Component(/* ... */)
export class Checkout {
  private analytics = inject(AnalyticsService);

  onAction() {
    this.analytics.trackEvent('action');
  }
}
```

## [Accessing Document via DI](#accessing-document-via-di)

When working with server-side rendering, you should avoid directly referencing browser-specific globals like `document`. Instead, use the [`DOCUMENT`](api/core/DOCUMENT) token to access the document object in a platform-agnostic way.

```
import {inject, DOCUMENT, Service} from '@angular/core';

@Service()
export class CanonicalLinkService {
  private readonly document = inject(DOCUMENT);

  // During server rendering, inject a <link rel="canonical"> tag
  // so the generated HTML includes the correct canonical URL
  setCanonical(href: string): void {
    const link = this.document.createElement('link');
    link.rel = 'canonical';
    link.href = href;
    this.document.head.appendChild(link);
  }
}
```

**HELPFUL:** For managing meta tags, Angular provides the [`Meta`](/api/platform-browser/Meta) service.

## [Accessing Request and Response via DI](#accessing-request-and-response-via-di)

The `@angular/core` package provides several tokens for interacting with the server-side rendering environment. These tokens give you access to crucial information and objects within your Angular application during SSR.

* **[`REQUEST`](api/core/REQUEST "API reference"):** Provides access to the current request object, which is of type [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) from the Web API. This allows you to access headers, cookies, and other request information.
* **[`RESPONSE_INIT`](api/core/RESPONSE_INIT "API reference"):** Provides access to the response initialization options, which is of type [`ResponseInit`](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#parameters) from the Web API. This allows you to set headers and the status code for the response dynamically. Use this token to set headers or status codes that need to be determined at runtime.
* **[`REQUEST_CONTEXT`](api/core/REQUEST_CONTEXT "API reference"):** Provides access to additional context related to the current request. This context can be passed as the second parameter of the [`handle`](api/ssr/AngularAppEngine#handle "API reference") function. Typically, this is used to provide additional request-related information that is not part of the standard Web API.

```
import {inject, REQUEST} from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `<h1>My Component</h1>`,
})
export class MyComponent {
  constructor() {
    const request = inject(REQUEST);
    console.log(request?.url);
  }
}
```

```
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "outputMode": "static"
          }
        }
      }
    }
  }
}
```

## [Caching data when using HttpClient](#caching-data-when-using-httpclient)

[`HttpClient`](/api/common/http/HttpClient) caches outgoing network requests when running on the server. This information is serialized and transferred to the browser as part of the initial HTML sent from the server. In the browser, [`HttpClient`](/api/common/http/HttpClient) checks whether it has data in the cache and if so, reuses it instead of making a new HTTP request during initial application rendering. [`HttpClient`](/api/common/http/HttpClient) stops using the cache once an application becomes [stable](api/core/ApplicationRef#isStable) while running in a browser.

### [Configuring the response body size limit](#configuring-the-response-body-size-limit)

When [`HttpClient`](/api/common/http/HttpClient) uses the default fetch backend during server-side rendering, Angular limits each response body to 1 MB. This limit prevents the server from buffering unexpectedly large responses during rendering. If a response exceeds the configured limit, the request fails with the [NG02825](errors/NG02825) error.

If your application needs to fetch larger responses during server rendering, set `maxResponseBodySize` in the `provideServerRendering` options:

```
import {provideServerRendering, withRoutes} from '@angular/ssr';
import {serverRoutes} from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(
      {
        maxResponseBodySize: 5 * 1024 * 1024, // 5MB
      },
      withRoutes(serverRoutes),
    ),
  ],
};
```

```
import {
  bootstrapApplication,
  provideClientHydration,
  withHttpTransferCacheOptions,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [
    provideClientHydration(
      withHttpTransferCacheOptions({
        includeHeaders: ['ETag', 'Cache-Control'],
        filter: (req) => !req.url.includes('/api/profile'),
        includePostRequests: true,
        includeRequestsWithAuthHeaders: false,
      }),
    ),
  ],
});
```

### [`includeHeaders`](#includeheaders)

Specifies which headers from the server response should be included in cached entries.\
No headers are included by default.

```
withHttpTransferCacheOptions({
  includeHeaders: ['ETag', 'Cache-Control'],
});
```

**IMPORTANT:** Avoid including sensitive headers like authentication tokens. These can leak user‑specific data between requests.

Including `Cache-Control` in `includeHeaders` only makes that header available on the hydrated response. Angular already evaluates `Cache-Control` headers automatically when deciding whether a request or response is eligible for transfer cache.

### [`includePostRequests`](#includepostrequests)

By default, only `GET` and `HEAD` requests are cached.\
You can enable caching for `POST` requests when they are used as read operations such as GraphQL queries.

```
withHttpTransferCacheOptions({
  includePostRequests: true,
});
```

Use this only when `POST` requests are **idempotent** and safe to reuse between server and client renders.

### [`includeRequestsWithAuthHeaders`](#includerequestswithauthheaders)

Determines whether requests containing `Authorization`, `Proxy‑Authorization`, or `Cookie` headers are eligible for caching.\
By default, these are excluded to prevent caching user‑specific responses.

```
withHttpTransferCacheOptions({
  includeRequestsWithAuthHeaders: true,
});
```

Enable only when authentication headers do **not** affect the response content (for example, public tokens for analytics APIs).

### [`includeRequestsWithCredentials`](#includerequestswithcredentials)

Determines whether requests sent using `withCredentials` or Fetch API `credentials` modes (`include` or `same-origin`) are eligible for caching.\
By default, these are excluded to prevent caching user‑specific responses.

```
withHttpTransferCacheOptions({
  includeRequestsWithCredentials: true,
});
```

Enable only when credentialed requests return responses that are safe to cache.

### [`includeNonCacheableRequests`](#includenoncacheablerequests)

Determines whether requests and responses containing `Cache-Control` directives that forbid caching (`no-store`, `no-cache`, or `private`), responses with a `Set-Cookie` header, or requests using Fetch API `cache` options (`no-store` or `no-cache`), are eligible for caching.\
By default, these are excluded to respect HTTP caching controls.

```
withHttpTransferCacheOptions({
  includeNonCacheableRequests: true,
});
```

Enable only when you need to bypass cache-control restrictions for transfer caching.

### [Per‑request overrides](#perrequest-overrides)

You can override caching behavior for a specific request using the `transferCache` request option.

```
// Include specific headers for this request
http.get('/api/profile', {transferCache: {includeHeaders: ['CustomHeader']}});
```

### [Disabling caching](#disabling-caching)

You can disable HTTP caching of requests sent from the server either globally or individually.

#### [Globally](#globally)

To disable caching for all requests in your application, use the [`withNoHttpTransferCache`](/api/platform-browser/withNoHttpTransferCache) feature:

```
import {
  bootstrapApplication,
  provideClientHydration,
  withNoHttpTransferCache,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration(withNoHttpTransferCache())],
});
```

#### [Filtering](#filtering)

You can also selectively disable caching for certain requests using the [`filter`](api/common/http/HttpTransferCacheOptions) option in [`withHttpTransferCacheOptions`](/api/platform-browser/withHttpTransferCacheOptions). For example, you can disable caching for a specific API endpoint:

```
import {
  bootstrapApplication,
  provideClientHydration,
  withHttpTransferCacheOptions,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [
    provideClientHydration(
      withHttpTransferCacheOptions({
        filter: (req) => !req.url.includes('/api/sensitive-data'),
      }),
    ),
  ],
});
```

Use this option to exclude endpoints with user‑specific or dynamic data (for example `/api/profile`).

#### [Per-request](#per-request)

To disable caching for an individual request, you can specify the [`transferCache`](api/common/http/HttpRequest#transferCache) option in an [`HttpRequest`](/api/common/http/HttpRequest).

```
httpClient.get('/api/sensitive-data', {transferCache: false});
```

`HttpTransferCache` does not cache requests or responses that explicitly opt out of caching. Angular skips transfer cache entries when a request includes a `Cache-Control` header with `no-store`, `no-cache`, or `private`, or when the request uses the Fetch API `cache` option set to `no-store` or `no-cache`. Responses with `Cache-Control: no-store`, `Cache-Control: no-cache`, or `Cache-Control: private` are also not stored in the transfer cache. Responses that include a `Set-Cookie` header are likewise not stored, as they typically carry user-specific state.

**NOTE:** If your application uses different HTTP origins to make API calls on the server and on the client, the [`HTTP_TRANSFER_CACHE_ORIGIN_MAP`](/api/common/http/HTTP_TRANSFER_CACHE_ORIGIN_MAP) token allows you to establish a mapping between those origins, so that `HttpTransferCache` feature can recognize those requests as the same ones and reuse the data cached on the server during hydration on the client.

## [Configuring a server](#configuring-a-server)

### [Node.js](#nodejs)

The `@angular/ssr/node` extends `@angular/ssr` specifically for Node.js environments. It provides APIs that make it easier to implement server-side rendering within your Node.js application. For a complete list of functions and usage examples, refer to the [`@angular/ssr/node` API reference](api/ssr/node/AngularNodeAppEngine) API reference.

### server.ts

```
import {
  AngularNodeAppEngine,
  createNodeRequestHandler,
  writeResponseToNodeResponse,
} from '@angular/ssr/node';
import express from 'express';

const app = express();
const angularApp = new AngularNodeAppEngine();

app.use((req, res, next) => {
  angularApp
    .handle(req)
    .then((response) => {
      if (response) {
        writeResponseToNodeResponse(response, res);
      } else {
        next(); // Pass control to the next middleware
      }
    })
    .catch(next);
});

/**
 * The request handler used by the Angular CLI (dev-server and during build).
 */
export const reqHandler = createNodeRequestHandler(app);
```

### [Non-Node.js](#non-nodejs)

The `@angular/ssr` provides essential APIs for server-side rendering your Angular application on platforms other than Node.js. It leverages the standard [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects from the Web API, enabling you to integrate Angular SSR into various server environments. For detailed information and examples, refer to the [`@angular/ssr` API reference](api/ssr/AngularAppEngine).

### server.ts

```
import {AngularAppEngine, createRequestHandler} from '@angular/ssr';

const angularApp = new AngularAppEngine();

/**
 * This is a request handler used by the Angular CLI (dev-server and during build).
 */
export const reqHandler = createRequestHandler(async (req: Request) => {
  const res: Response | null = await angularApp.handle(req);

  // ...
});
```

## [Security](#security)

For detailed information on preventing Server-Side Request Forgery (SSRF) and configuring allowed hosts, see the [Server-side security](best-practices/security#preventing-server-side-request-forgery-ssrf) guide.

----
url: https://angular.dev/api/core/ListenerOptions
----

```
    
      interface ListenerOptions {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/createPlatformFactory
----

## [API](/api/core/createPlatformFactory#api)

```
function createPlatformFactory(
  parentPlatformFactory:
    ((extraProviders?: StaticProvider[] | undefined) => PlatformRef) | null,
  name: string,
  providers?: StaticProvider[],
): (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
```

@paramparentPlatformFactory`((extraProviders?: StaticProvider[] | undefined) => PlatformRef) | null`

Another platform factory to modify. Allows you to compose factories to build up configurations that might be required by different libraries or parts of the application.

@paramname`string`

Identifies the new platform factory.

@paramproviders`StaticProvider[]`

A set of dependency providers for platforms created with the new factory.

@returns`(extraProviders?: StaticProvider[] | undefined) => PlatformRef`

Jump to details

----
url: https://angular.dev/api/forms/signals/REQUIRED
----

```
    
      const REQUIRED: MetadataKey<Signal<boolean>, boolean, boolean>;
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/NoopAnimationsModule
----

Deprecation warning

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

## [API](/api/platform-browser/animations/NoopAnimationsModule#api)

```
    
      class NoopAnimationsModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/Route
----

```
    
      interface Route {}
    
    
```

Used to define a page title for the route. This can be a static string or an [`Injectable`](/api/core/Injectable) that implements [`Resolve`](/api/router/Resolve).

The path to match against. Cannot be used together with a custom `matcher` function. A URL string that uses router matching notation. Can be a wild card (`**`) that matches any URL (see Usage Notes below). Default is "/" (the root path).

The path-matching strategy, one of 'prefix' or 'full'. Default is 'prefix'.

By default, the router checks URL elements from the left to see if the URL matches a given path and stops when there is a config match. Importantly there must still be a config match for each segment of the URL. For example, '/team/11/user' matches the prefix 'team/:id' if one of the route's children matches the segment 'user'. That is, the URL '/team/11/user' matches the config `{path: 'team/:id', children: [{path: ':user', component: User}]}` but does not match when there are no children as in `{path: 'team/:id', component: Team}`.

The path-match strategy 'full' matches against the entire URL. It is important to do this when redirecting empty-path routes. Otherwise, because an empty path is a prefix of any URL, the router would apply the redirect even when navigating to the redirect destination, creating an endless loop.

A custom URL-matching function. Cannot be used together with `path`.

The component to instantiate when the path matches. Can be empty if child routes specify components.

An object specifying a lazy-loaded component.

A URL or function that returns a URL to redirect to when the path matches.

Absolute if the URL begins with a slash (/) or the function returns a [`UrlTree`](/api/router/UrlTree), otherwise relative to the path URL.

The [`RedirectFunction`](/api/router/RedirectFunction) is run in an injection context so it can call [`inject`](/api/core/inject) to get any required dependencies.

When not present, router does not redirect.

Name of a [`RouterOutlet`](/api/router/RouterOutlet) object where the component can be placed when the path matches.

An array of [`CanActivateFn`](/api/router/CanActivateFn) or DI tokens used to look up [`CanActivate()`](/api/router/CanActivate) handlers, in order to determine if the current user is allowed to activate the component. By default, any user can activate.

When using a function rather than DI tokens, the function can call [`inject`](/api/core/inject) to get any required dependencies. This [`inject`](/api/core/inject) call must be done in a synchronous context.

An array of [`CanMatchFn`](/api/router/CanMatchFn) or DI tokens used to look up [`CanMatch()`](/api/router/CanMatch) handlers, in order to determine if the current user is allowed to match the [`Route`](/api/router/Route). By default, any route can match.

When using a function rather than DI tokens, the function can call [`inject`](/api/core/inject) to get any required dependencies. This [`inject`](/api/core/inject) call must be done in a synchronous context.

An array of [`CanActivateChildFn`](/api/router/CanActivateChildFn) or DI tokens used to look up [`CanActivateChild()`](/api/router/CanActivateChild) handlers, in order to determine if the current user is allowed to activate a child of the component. By default, any user can activate a child.

When using a function rather than DI tokens, the function can call [`inject`](/api/core/inject) to get any required dependencies. This [`inject`](/api/core/inject) call must be done in a synchronous context.

An array of [`CanDeactivateFn`](/api/router/CanDeactivateFn) or DI tokens used to look up [`CanDeactivate()`](/api/router/CanDeactivate) handlers, in order to determine if the current user is allowed to deactivate the component. By default, any user can deactivate.

When using a function rather than DI tokens, the function can call [`inject`](/api/core/inject) to get any required dependencies. This [`inject`](/api/core/inject) call must be done in a synchronous context.

@deprecated

Use `canMatch` instead

An array of [`CanLoadFn`](/api/router/CanLoadFn) or DI tokens used to look up [`CanLoad()`](/api/router/CanLoad) handlers, in order to determine if the current user is allowed to load the component. By default, any user can load.

When using a function rather than DI tokens, the function can call [`inject`](/api/core/inject) to get any required dependencies. This [`inject`](/api/core/inject) call must be done in a synchronous context.

Additional developer-defined data provided to the component via [`ActivatedRoute`](/api/router/ActivatedRoute). By default, no additional data is passed.

A map of DI tokens used to look up data resolvers. See [`Resolve`](/api/router/Resolve).

A function that returns a record of resources. This function is executed during the Main Loading Phase of a navigation.

An array of child [`Route`](/api/router/Route) objects that specifies a nested route configuration.

An object specifying lazy-loaded child routes.

A [`Provider`](/api/core/Provider) array to use for this [`Route`](/api/router/Route) and its `children`.

The [`Router`](/api/router/Router) will create a new [`EnvironmentInjector`](/api/core/EnvironmentInjector) for this [`Route`](/api/router/Route) and use it for this [`Route`](/api/router/Route) and its `children`. If this route also has a `loadChildren` function which returns an `NgModuleRef`, this injector will be used as the parent of the lazy loaded module.

## [Description](#description)

A configuration object that defines a single route. A set of routes are collected in a [`Routes`](/api/router/Routes) array to define a [`Router`](/api/router/Router) configuration. The router attempts to match segments of a given URL against each route, using the configuration options defined in this object.

Supports static, parameterized, redirect, and wildcard routes, as well as custom route data and resolve methods.

For detailed usage information, see the [Routing Guide](guide/routing/common-router-tasks).

## [Usage Notes](#usage-notes)

### [Simple Configuration](#simple-configuration)

The following route specifies that when navigating to, for example, `/team/11/user/bob`, the router creates the 'Team' component with the 'User' child component in it.

```
[{
  path: 'team/:id',
 component: Team,
  children: [{
    path: 'user/:name',
    component: User
  }]
}]
```

### [Multiple Outlets](#multiple-outlets)

The following route creates sibling components with multiple outlets. When navigating to `/team/11(aux:chat/jim)`, the router creates the 'Team' component next to the 'Chat' component. The 'Chat' component is placed into the 'aux' outlet.

```
[{
  path: 'team/:id',
  component: Team
}, {
  path: 'chat/:user',
  component: Chat
  outlet: 'aux'
}]
```

### [Wild Cards](#wild-cards)

The following route uses wild-card notation to specify a component that is always instantiated regardless of where you navigate to.

```
[{
  path: '**',
  component: WildcardComponent
}]
```

### [Redirects](#redirects)

The following route uses the `redirectTo` property to ignore a segment of a given URL when looking for a child path.

When navigating to '/team/11/legacy/user/jim', the router changes the URL segment '/team/11/legacy/user/jim' to '/team/11/user/jim', and then instantiates the Team component with the User child component in it.

```
[{
  path: 'team/:id',
  component: Team,
  children: [{
    path: 'legacy/user/:name',
    redirectTo: 'user/:name'
  }, {
    path: 'user/:name',
    component: User
  }]
}]
```

The redirect path can be relative, as shown in this example, or absolute. If we change the `redirectTo` value in the example to the absolute URL segment '/user/:name', the result URL is also absolute, '/user/jim'.

### [Empty Path](#empty-path)

Empty-path route configurations can be used to instantiate components that do not 'consume' any URL segments.

In the following configuration, when navigating to `/team/11`, the router instantiates the 'AllUsers' component.

```
[{
  path: 'team/:id',
  component: Team,
  children: [{
    path: '',
    component: AllUsers
  }, {
    path: 'user/:name',
    component: User
  }]
}]
```

Empty-path routes can have children. In the following example, when navigating to `/team/11/user/jim`, the router instantiates the wrapper component with the user component in it.

Note that an empty path route inherits its parent's parameters and data.

```
[{
  path: 'team/:id',
  component: Team,
  children: [{
    path: '',
    component: WrapperCmp,
    children: [{
      path: 'user/:name',
      component: User
    }]
  }]
}]
```

### [Matching Strategy](#matching-strategy)

The default path-match strategy is 'prefix', which means that the router checks URL elements from the left to see if the URL matches a specified path. For example, '/team/11/user' matches 'team/:id'.

```
[{
  path: '',
  pathMatch: 'prefix', //default
  redirectTo: 'main'
}, {
  path: 'main',
  component: Main
}]
```

You can specify the path-match strategy 'full' to make sure that the path covers the whole unconsumed URL. It is important to do this when redirecting empty-path routes. Otherwise, because an empty path is a prefix of any URL, the router would apply the redirect even when navigating to the redirect destination, creating an endless loop.

In the following example, supplying the 'full' `pathMatch` strategy ensures that the router applies the redirect if and only if navigating to '/'.

```
[{
  path: '',
  pathMatch: 'full',
  redirectTo: 'main'
}, {
  path: 'main',
  component: Main
}]
```

### [Componentless Routes](#componentless-routes)

You can share parameters between sibling components. For example, suppose that two sibling components should go next to each other, and both of them require an ID parameter. You can accomplish this using a route that does not specify a component at the top level.

In the following example, 'MainChild' and 'AuxChild' are siblings. When navigating to 'parent/10/(a//aux:b)', the route instantiates the main child and aux child components next to each other. For this to work, the application component must have the primary and aux outlets defined.

```
[{
   path: 'parent/:id',
   children: [
     { path: 'a', component: MainChild },
     { path: 'b', component: AuxChild, outlet: 'aux' }
   ]
}]
```

The router merges the parameters, data, and resolve of the componentless parent into the parameters, data, and resolve of the children.

This is especially useful when child components are defined with an empty path string, as in the following example. With this configuration, navigating to '/parent/10' creates the main child and aux components.

```
[{
   path: 'parent/:id',
   children: [
     { path: '', component: MainChild },
     { path: '', component: AuxChild, outlet: 'aux' }
   ]
}]
```

### [Lazy Loading](#lazy-loading)

Lazy loading speeds up application load time by splitting the application into multiple bundles and loading them on demand. To use lazy loading, provide the `loadChildren` property in the [`Route`](/api/router/Route) object, instead of the `children` property.

Given the following example route, the router will lazy load the associated module on demand using the browser native import system.

```
[{
  path: 'lazy',
  loadChildren: () => import('./lazy-route/lazy.module').then(mod => mod.LazyModule),
}];
```

Jump to details

----
url: https://angular.dev/api/upgrade/static/testing/createAngularJSTestingModule
----

```
function createAngularJSTestingModule(angularModules: any[]): string;
```

@paramangularModules`any[]`

a collection of Angular modules to include in the configuration.

@returns`string`

## [Description](#description)

A helper function to use when unit testing AngularJS services that depend upon downgraded Angular services.

This function returns an AngularJS module that is configured to wire up the AngularJS and Angular injectors without the need to actually bootstrap a hybrid application. This makes it simpler and faster to unit test services.

Use the returned AngularJS module in a call to [`angular.mocks.module`](https://docs.angularjs.org/api/ngMock/function/angular.mock.module) to include this module in the unit test injector.

In the following code snippet, we are configuring the `$injector` with two modules: The AngularJS `ng1AppModule`, which is the AngularJS part of our hybrid application and the `Ng2AppModule`, which is the Angular part.

```
beforeEach(module(createAngularJSTestingModule([Ng2AppModule])));
  beforeEach(module(ng1AppModule.name));
```

Once this is done we can get hold of services via the AngularJS `$injector` as normal. Services that are (or have dependencies on) a downgraded Angular service, will be instantiated as needed by the Angular root [`Injector`](/api/core/Injector).

In the following code snippet, `heroesService` is a downgraded Angular service that we are accessing from AngularJS.

```
it('should have access to the HeroesService', inject((heroesService: HeroesService) => {
    expect(heroesService).toBeDefined();
  }));
```

This helper is for testing services not components. For Component testing you must still bootstrap a hybrid app. See `UpgradeModule` or `downgradeModule` for more information.

The resulting configuration does not wire up AngularJS digests to Zone hooks. It is the responsibility of the test writer to call `$rootScope.$apply`, as necessary, to trigger AngularJS handlers of async events from Angular.

The helper sets up global variables to hold the shared Angular and AngularJS injectors.

* Only call this helper once per spec.
* Do not use [`createAngularJSTestingModule`](/api/upgrade/static/testing/createAngularJSTestingModule) in the same spec as [`createAngularTestingModule`](/api/upgrade/static/testing/createAngularTestingModule).

Here is the example application and its unit tests that use [`createAngularTestingModule`](/api/upgrade/static/testing/createAngularTestingModule) and [`createAngularJSTestingModule`](/api/upgrade/static/testing/createAngularJSTestingModule).

Jump to details

----
url: https://angular.dev/api/aria/menu/MenuTrigger
----

```
    
      class MenuTrigger<V> {}
    
    
```

A reference to the host element.

The directionality (LTR / RTL) context for the application (or a subtree of it).

The menu associated with the trigger.

Whether the menu is expanded.

Whether the menu trigger has a popup.

Whether the menu trigger is disabled.

Whether the menu trigger is soft disabled.

Opens the menu focusing on the first menu item.

@returns`void`

Closes the menu.

@returns`void`

## [Description](#description)

A trigger for a menu.

The `ngMenuTrigger` directive is used to open and close menus. It can be applied to any interactive element (e.g., a button) to associate it with a `ngMenu` instance. It also supports linking to sub-menus when applied to a `ngMenuItem`.

```
<button ngMenuTrigger [menu]="myMenu">Open Menu</button>

<div ngMenu #myMenu="ngMenu">
  <div ngMenuItem value="Item 1">Item 1</div>
  <div ngMenuItem value="Item 2">Item 2</div>
</div>
```

Jump to details

----
url: https://angular.dev/ecosystem/rxjs-interop/output-interop
----

**TIP:** This guide assumes you're familiar with [component and directive outputs](guide/components/outputs).

The `@angular/core/rxjs-interop` package offers two APIs related to component and directive outputs.

## [Creating an output based on an RxJS Observable](/ecosystem/rxjs-interop/output-interop#creating-an-output-based-on-an-rxjs-observable)

The [`outputFromObservable`](/api/core/rxjs-interop/outputFromObservable) lets you create a component or directive output that emits based on an RxJS observable:

```
import {Directive} from '@angular/core';
import {outputFromObservable} from '@angular/core/rxjs-interop';

@Directive(/* ... */)
class Draggable {
  pointerMoves$: Observable<PointerMovements> = listenToPointerMoves();

  // Whenever `pointerMoves$` emits, the `pointerMove` event fires.
  pointerMove = outputFromObservable(this.pointerMoves$);
}
```

The [`outputFromObservable`](/api/core/rxjs-interop/outputFromObservable) function has special meaning to the Angular compiler. **You may only call [`outputFromObservable`](/api/core/rxjs-interop/outputFromObservable) in component and directive property initializers.**

When you `subscribe` to the output, Angular automatically forwards the subscription to the underlying observable. Angular stops forwarding values when the component or directive is destroyed.

**HELPFUL:** Consider using [`output()`](/api/core/output) directly if you can emit values imperatively.

## [Creating an RxJS Observable from a component or directive output](/ecosystem/rxjs-interop/output-interop#creating-an-rxjs-observable-from-a-component-or-directive-output)

The [`outputToObservable`](/api/core/rxjs-interop/outputToObservable) function lets you create an RxJS observable from a component output.

```
import {outputToObservable} from '@angular/core/rxjs-interop';

@Component(/*...*/)
class CustomSlider {
    valueChange = output<number>();
}

// Instance reference to `CustomSlider`.
const slider: CustomSlider = createSlider();

outputToObservable(slider.valueChange) // Observable<number>
    .pipe(...)
    .subscribe(...);
```

**HELPFUL:** Consider using the `subscribe` method on [`OutputRef`](/api/core/OutputRef) directly if it meets your needs.

----
url: https://angular.dev/api/core/testing/DeferBlockState
----

```
    
      enum DeferBlockState {}
    
    
```

The placeholder block content is rendered

The loading block content is rendered

The main content block content is rendered

The error block content is rendered

Jump to details

----
url: https://angular.dev/api/forms/PristineChangeEvent
----

```
    
      class PristineChangeEvent extends ControlEvent {}
    
    
```

@parampristine`boolean`

@paramsource`AbstractControl<any, any, any>`

@returns`PristineChangeEvent`

Jump to details

----
url: https://angular.dev/best-practices/performance/lazy-loading-services
----

**IMPORTANT:** For lazy loading to work, the service you load must be auto-provided. Decorate it with either `@Injectable({providedIn: 'root'})` or [`@Service()`](guide/di/creating-and-using-services#using-the-service-vs-injectable-decorator). Without auto-provisioning, Angular has no way to construct the service after it loads.

Angular's [`injectAsync`](/api/core/injectAsync) function lets you load a service on demand, only when it's actually needed. This is useful when a service depends on a large library or rarely used feature, and you don't want to pay for it on the initial page load.

When you use [`injectAsync`](/api/core/injectAsync), the service's code is split out by your bundler into a separate JavaScript chunk and downloaded the first time you ask for the instance. Once loaded, Angular resolves the service through the regular DI system, so it can still depend on other injectables and behaves like any other singleton.

## [Lazily injecting a service](#lazily-injecting-a-service)

Imagine a `ReportExporter` that depends on a heavy spreadsheet library. Most users open the report; only a few click **Export**. Load the exporter on demand:

```
import {Component, injectAsync} from '@angular/core';

@Component({
  selector: 'app-report',
  template: `<button (click)="export()">Export</button>`,
})
export class Report {
  private exporter = injectAsync(() => import('./report-exporter').then((m) => m.ReportExporter));

  async export() {
    const exporter = await this.exporter();
    exporter.export();
  }
}
```

The first call to `this.exporter()` triggers the dynamic import and resolves the service from DI. Subsequent calls reuse the same promise, so the chunk is only fetched once.

If the lazy-loaded service is the [default export](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/export#using_the_default_export), pass the dynamic import directly, Angular unwraps the `default` for you:

### report-exporter.ts

```
@Service()
export default class ReportExporter {
  /* … */
}
```

### report.ts

```
private exporter = injectAsync(() => import('./report-exporter'));
```

## [Prefetching the dependency](#prefetching-the-dependency)

By default, the lazy chunk is only fetched when you invoke the returned function. You can start the download earlier by passing a `prefetch` trigger in the options. A trigger is any function that returns a `Promise`, when it resolves, Angular kicks off the loader.

Angular ships with [`onIdle`](/api/core/onIdle), a built-in trigger that waits until the browser becomes idle:

```
import {Component, injectAsync, onIdle} from '@angular/core';

@Component({/* … */})
export class Report {
  private exporter = injectAsync(() => import('./report-exporter').then((m) => m.ReportExporter), {
    prefetch: onIdle,
  });
}
```

You can also configure [`onIdle`](/api/core/onIdle) with a maximum wait time so the prefetch always happens within a known window, even on busy pages:

```
injectAsync(loader, {prefetch: () => onIdle({timeout: 1_000})});
```

**NOTE:** Prefetching is opportunistic. If the user invokes the feature before the prefetch fires, Angular still loads the dependency immediately and resolves your `await` as soon as it's ready.

## [Provide a custom prefetch trigger](#provide-a-custom-prefetch-trigger)

A [`PrefetchTrigger`](/api/core/PrefetchTrigger) is just a function that returns a promise, the loader runs as soon as the promise resolves. Use this to align prefetching with your own signals, such as a hover or a scheduler tick:

```
import {PrefetchTrigger} from '@angular/core';

export function onHover(target: HTMLElement): PrefetchTrigger {
  return () =>
    new Promise<void>((resolve) => {
      target.addEventListener('pointerenter', () => resolve(), {once: true});
    });
}
```

----
url: https://angular.dev/tutorials/first-app/10-routing
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/09-services)[](/tutorials/first-app/11-details-page)

[](/tutorials/first-app/09-services)[](/tutorials/first-app/11-details-page)

This tutorial lesson demonstrates how to add routes to your app.

[](https://www.youtube.com/watch?v=r5DEBMuStPw\&autoplay=1)

**IMPORTANT:** We recommend using your local environment to learn routing.

## [What you'll learn](#what-youll-learn)

At the end of this lesson your application will have support for routing.

## [Conceptual preview of routing](#conceptual-preview-of-routing)

This tutorial introduces routing in Angular. Routing is the ability to navigate from one component in the application to another. In [Single Page Applications (SPA)](guide/routing), only parts of the page are updated to represent the requested view for the user.

The [Angular Router](guide/routing) enables users to declare routes and specify which component should be displayed on the screen if that route is requested by the application.

In this lesson, you will enable routing in your application to navigate to the details page.

1. ### [Create a default details component](#create-a-default-details-component-)

   1. From the terminal, enter the following command to create the `Details`:

      ```
      ng generate component details
      ```

      This component will represent the details page that provides more information on a given housing location.

2. ### [Add routing to the application](#add-routing-to-the-application)

   1. In the `src/app` directory, create a file called `routes.ts`. This file is where we will define the routes in the application.

   2. In `main.ts`, make the following updates to enable routing in the application:

      1. Import the routes file and the [`provideRouter`](/api/router/provideRouter) function:

         ### Import routing details in src/main.ts

         ```
         import {bootstrapApplication} from '@angular/platform-browser';
         import {App} from './app/app';
         import {provideRouter} from '@angular/router';
         import routeConfig from './app/routes';

         bootstrapApplication(App, {
           providers: [provideRouter(routeConfig)],
         }).catch((err) => console.error(err));
         ```

      2. Update the call to [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) to include the routing configuration:

         ### Add router configuration in src/main.ts

         ```
         import {bootstrapApplication} from '@angular/platform-browser';
         import {App} from './app/app';
         import {provideRouter} from '@angular/router';
         import routeConfig from './app/routes';

         bootstrapApplication(App, {
           providers: [provideRouter(routeConfig)],
         }).catch((err) => console.error(err));
         ```

   3. In `src/app/app.ts`, update the component to use routing:

      1. Add file level imports for the router directives [`RouterOutlet`](/api/router/RouterOutlet) and [`RouterLink`](/api/router/RouterLink):

         ### Import router directives in src/app/app.ts

         ```
         import {Component} from '@angular/core';
         import {Home} from './home/home';
         import {RouterLink, RouterOutlet} from '@angular/router';
         @Component({
           selector: 'app-root',
           imports: [Home, RouterOutlet, RouterLink],
           template: `
             <main>
               <a [routerLink]="['/']">
                 <header class="brand-name">
                   <img class="brand-logo" src="/public/logo.svg" alt="logo" aria-hidden="true" />
                 </header>
               </a>
               <section class="content">
                 <router-outlet />
               </section>
             </main>
           `,
           styleUrls: ['./app.css'],
         })
         export class App {
           title = 'homes';
         }
         ```

      2. Add [`RouterOutlet`](/api/router/RouterOutlet) and [`RouterLink`](/api/router/RouterLink) to the [`@Component`](/api/core/Component) metadata imports

         ### Add router directives to component imports in src/app/app.ts

         ```
         import {Component} from '@angular/core';
         import {Home} from './home/home';
         import {RouterLink, RouterOutlet} from '@angular/router';
         @Component({
           selector: 'app-root',
           imports: [Home, RouterOutlet, RouterLink],
           template: `
             <main>
               <a [routerLink]="['/']">
                 <header class="brand-name">
                   <img class="brand-logo" src="/public/logo.svg" alt="logo" aria-hidden="true" />
                 </header>
               </a>
               <section class="content">
                 <router-outlet />
               </section>
             </main>
           `,
           styleUrls: ['./app.css'],
         })
         export class App {
           title = 'homes';
         }
         ```

      3. In the `template` property, replace the `<app-home />` tag with the `<router-outlet>` directive and add a link back to the home page. Your code should match this code:

         ### Add router-outlet in src/app/app.ts

         ```
         import {Component} from '@angular/core';
         import {Home} from './home/home';
         import {RouterLink, RouterOutlet} from '@angular/router';
         @Component({
           selector: 'app-root',
           imports: [Home, RouterOutlet, RouterLink],
           template: `
             <main>
               <a [routerLink]="['/']">
                 <header class="brand-name">
                   <img class="brand-logo" src="/public/logo.svg" alt="logo" aria-hidden="true" />
                 </header>
               </a>
               <section class="content">
                 <router-outlet />
               </section>
             </main>
           `,
           styleUrls: ['./app.css'],
         })
         export class App {
           title = 'homes';
         }
         ```

3. ### [Add route to new component](#add-route-to-new-component)

   In the previous step you removed the reference to the `<app-home>` component in the template. In this step, you will add a new route to that component.

   1. In `routes.ts`, perform the following updates to create a route.

      1. Add a file level imports for the `Home`, `Details` and the [`Routes`](/api/router/Routes) type that you'll use in the route definitions.

         ### Import components and Routes

         ```
         import {Routes} from '@angular/router';
         import {Home} from './home/home';
         import {Details} from './details/details';

         const routeConfig: Routes = [
           {
             path: '',
             component: Home,
             title: 'Home page',
           },
           {
             path: 'details/:id',
             component: Details,
             title: 'Home details',
           },
         ];

         export default routeConfig;
         ```

      2. Define a variable called `routeConfig` of type [`Routes`](/api/router/Routes) and define two routes for the app:

         ### Add routes to the app

         ```
         import {Routes} from '@angular/router';
         import {Home} from './home/home';
         import {Details} from './details/details';

         const routeConfig: Routes = [
           {
             path: '',
             component: Home,
             title: 'Home page',
           },
           {
             path: 'details/:id',
             component: Details,
             title: 'Home details',
           },
         ];

         export default routeConfig;
         ```

         The entries in the `routeConfig` array represent the routes in the application. The first entry navigates to the `Home` whenever the url matches `''`. The second entry uses some special formatting that will be revisited in a future lesson.

   2. Save all changes and confirm that the application works in the browser. The application should still display the list of housing locations.

**SUMMARY:** In this lesson, you enabled routing in your app as well as defined new routes. Now your app can support navigation between views. In the next lesson, you will learn to navigate to the "details" page for a given housing location.

You are making great progress with your app, well done.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/api/forms/signals/MinDateValidationError
----

```
    
      class MinDateValidationError extends BaseNgValidationError {}
    
    
```

@paramminDate`Date`

@paramoptions`ValidationErrorOptions | undefined`

@returns`MinDateValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/guide/forms/reactive-forms
----

Reactive forms provide a model-driven approach to handling form inputs whose values change over time. This guide shows you how to create and update a basic form control, use multiple controls in a group, validate form values, and create dynamic forms where you can add or remove controls at runtime.

## [Overview of reactive forms](#overview-of-reactive-forms)

Reactive forms use an explicit and immutable approach to managing the state of a form at a given point in time. Each change to the form state returns a new state, which maintains the integrity of the model between changes. Reactive forms are built around observable streams, where form inputs and values are provided as streams that can be accessed synchronously.

Reactive forms also provide a straightforward path to testing because you are assured that your data is consistent and predictable when requested. Any consumers of these streams can safely manipulate the data.

Reactive forms differ from [template-driven forms](guide/forms/template-driven-forms) in distinct ways. Reactive forms provide synchronous access to the data model, immutability with observable operators, and change tracking through observable streams.

Template-driven forms allow direct access to modify data in your template, but are less explicit than reactive forms because they rely on directives embedded in the template, along with mutable data to track changes asynchronously. See the [Forms Overview](guide/forms) for detailed comparisons between the two paradigms.

## [Adding a basic form control](#adding-a-basic-form-control)

There are three steps to using form controls.

1. Generate a new component and register the reactive forms module. This module declares the reactive-form directives required to use reactive forms.
2. Instantiate a new [`FormControl`](/api/forms/FormControl).
3. Register the [`FormControl`](/api/forms/FormControl) in the template.

You can then display the form by adding the component to the template.

The following examples show how to add a single form control. In the example, the user enters their name into an input field, captures that input value, and displays the current value of the form control element.

1. ### [Generate a new component and import the ReactiveFormsModule](#generate-a-new-component-and-import-the-reactiveformsmodule)

   Use the CLI command `ng generate component` to generate a component in your project and import [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule) from the `@angular/forms` package and add it to your Component's `imports` array.

   ### name-editor.component.ts (excerpt)

   ```
   import {FormControl, ReactiveFormsModule} from '@angular/forms';

   @Component({
     selector: 'app-name-editor',
     templateUrl: './name-editor.component.html',
     styleUrls: ['./name-editor.component.css'],
     imports: [ReactiveFormsModule],
   })
   export class NameEditorComponent {
   ```

2. ### [Declare a FormControl instance](#declare-a-formcontrol-instance)

   Use the constructor of [`FormControl`](/api/forms/FormControl) to set its initial value, which in this case is an empty string. By creating these controls in your component class, you get immediate access to listen for, update, and validate the state of the form input.

   ### name-editor.component.ts

   ```
   import {Component} from '@angular/core';

   import {FormControl, ReactiveFormsModule} from '@angular/forms';

   @Component({
     selector: 'app-name-editor',
     templateUrl: './name-editor.component.html',
     styleUrls: ['./name-editor.component.css'],
     imports: [ReactiveFormsModule],
   })
   export class NameEditorComponent {
     name = new FormControl('');
   }
   ```

3. ### [Register the control in the template](#register-the-control-in-the-template)

   After you create the control in the component class, you must associate it with a form control element in the template. Update the template with the form control using the [`formControl`](/api/forms/SelectMultipleControlValueAccessor) binding provided by [`FormControlDirective`](/api/forms/FormControlDirective), which is also included in the [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule).

   ### name-editor.component.html

   ```
   <label for="name">Name: </label>
   <input id="name" type="text" [formControl]="name" />
   ```

   Using the template binding syntax, the form control is now registered to the `name` input element in the template. The form control and DOM element communicate with each other: the view reflects changes in the model, and the model reflects changes in the view.

4. ### [Display the component](#display-the-component)

   The [`FormControl`](/api/forms/FormControl) assigned to the `name` property is displayed when the `<app-name-editor>` component is added to a template.

   ### app.component.html (name editor)

   ```
   <app-name-editor />
   ```

### [Displaying a form control value](#displaying-a-form-control-value)

You can display the value in the following ways:

* Through the `valueChanges` observable where you can listen for changes in the form's value in the template using [`AsyncPipe`](/api/common/AsyncPipe) or in the component class using the `subscribe()` method
* With the `value` property, which gives you a snapshot of the current value

The following example shows you how to display the current value using interpolation in the template.

### name-editor.component.html (control value)

```
<p>Value: {{ name.value }}</p>
```

The displayed value changes as you update the form control element.

Reactive forms provide access to information about a given control through properties and methods provided with each instance. These properties and methods of the underlying [AbstractControl](api/forms/AbstractControl "API reference") class are used to control form state and determine when to display messages when handling [input validation](#validating-form-input "Learn more about validating form input").

Read about other [`FormControl`](/api/forms/FormControl) properties and methods in the [API Reference](api/forms/FormControl "Detailed syntax reference").

### [Replacing a form control value](#replacing-a-form-control-value)

Reactive forms have methods to change a control's value programmatically, which gives you the flexibility to update the value without user interaction. A form control instance provides a `setValue()` method that updates the value of the form control and validates the structure of the value provided against the control's structure. For example, when retrieving form data from a backend API or service, use the `setValue()` method to update the control to its new value, replacing the old value entirely.

The following example adds a method to the component class to update the value of the control to *Nancy* using the `setValue()` method.

### name-editor.component.ts (update value)

```
updateName() {
  this.name.setValue('Nancy');
}
```

Update the template with a button to simulate a name update. When you click the **Update Name** button, the value entered in the form control element is reflected as its current value.

### name-editor.component.html (update value)

```
<button type="button" (click)="updateName()">Update Name</button>
```

The form model is the source of truth for the control, so when you click the button, the value of the input is changed within the component class, overriding its current value.

**HELPFUL:** In this example, you're using a single control. When using the `setValue()` method with a [form group](#grouping-form-controls) or [form array](#creating-dynamic-forms) instance, the value needs to match the structure of the group or array.

## [Grouping form controls](#grouping-form-controls)

Forms typically contain several related controls. Reactive forms provide two ways of grouping multiple related controls into a single input form.

| Form groups | Details                                                                                                                                                                                                                                                |
| :---------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Form group  | Defines a form with a fixed set of controls that you can manage together. Form group basics are discussed in this section. You can also [nest form groups](#creating-nested-form-groups "See more about nesting groups") to create more complex forms. |
| Form array  | Defines a dynamic form, where you can add and remove controls at run time. You can also nest form arrays to create more complex forms. For more about this option, see [Creating dynamic forms](#creating-dynamic-forms).                              |

Just as a form control instance gives you control over a single input field, a form group instance tracks the form state of a group of form control instances (for example, a form). Each control in a form group instance is tracked by name when creating the form group. The following example shows how to manage multiple form control instances in a single group.

Generate a `ProfileEditor` component and import the [`FormGroup`](/api/forms/FormGroup) and [`FormControl`](/api/forms/FormControl) classes from the `@angular/forms` package.

```
ng generate component ProfileEditor
```

### profile-editor.component.ts (imports)

```
import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css'],
  imports: [ReactiveFormsModule],
})
export class ProfileEditorComponent {
```

To add a form group to this component, take the following steps.

1. Create a [`FormGroup`](/api/forms/FormGroup) instance.
2. Associate the [`FormGroup`](/api/forms/FormGroup) model and view.
3. Save the form data.

1) ### [Create a FormGroup instance](#create-a-formgroup-instance)

   Create a property in the component class named `profileForm` and set the property to a new form group instance. To initialize the form group, provide the constructor with an object of named keys mapped to their control.

   For the profile form, add two form control instances with the names `firstName` and `lastName`

   ### profile-editor.component.ts (form group)

   ```
   import {Component} from '@angular/core';
   import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';

   @Component({
     selector: 'app-profile-editor',
     templateUrl: './profile-editor.component.html',
     styleUrls: ['./profile-editor.component.css'],
     imports: [ReactiveFormsModule],
   })
   export class ProfileEditorComponent {
     profileForm = new FormGroup({
       firstName: new FormControl(''),
       lastName: new FormControl(''),
     });
   }
   ```

   The individual form controls are now collected within a group. A [`FormGroup`](/api/forms/FormGroup) instance provides its model value as an object reduced from the values of each control in the group. A form group instance has the same properties (such as `value` and `untouched`) and methods (such as `setValue()`) as a form control instance.

2) ### [Associate the FormGroup model and view](#associate-the-formgroup-model-and-view)

   A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view.

   ### profile-editor.component.html (template form group)

   ```
   <form [formGroup]="profileForm">
     <label for="first-name">First Name: </label>
     <input id="first-name" type="text" formControlName="firstName" />

     <label for="last-name">Last Name: </label>
     <input id="last-name" type="text" formControlName="lastName" />

   </form>
   ```

   Just as a form group contains a group of controls, the *profileForm* [`FormGroup`](/api/forms/FormGroup) is bound to the `form` element with the [`FormGroup`](/api/forms/FormGroup) directive, creating a communication layer between the model and the form containing the inputs. The [`formControlName`](/api/forms/SelectMultipleControlValueAccessor) input provided by the [`FormControlName`](/api/forms/FormControlName) directive binds each individual input to the form control defined in [`FormGroup`](/api/forms/FormGroup). The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value.

3) ### [Save form data](#save-form-data)

   The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make it available for further processing outside the component. The [`FormGroup`](/api/forms/FormGroup) directive listens for the [`submit`](/api/forms/signals/submit) event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method.

   ### profile-editor.component.html (submit event)

   ```
   <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
   ```

   The `onSubmit()` method in the `ProfileEditor` component captures the current value of `profileForm`. Use [`output()`](/api/core/output) to keep the form encapsulated and to provide the form value outside the component. The following example uses `console.warn` to log a message to the browser console.

   ### profile-editor.component.ts (submit method)

   ```
   onSubmit() {
     // TODO: Use output() with form value
     console.warn(this.profileForm.value);
   }
   ```

   The [`submit`](/api/forms/signals/submit) event is emitted by the `form` tag using the built-in DOM event. You trigger the event by clicking a button with [`submit`](/api/forms/signals/submit) type. This lets the user press the **Enter** key to submit the completed form.

   Use a `button` element to add a button to the bottom of the form to trigger the form submission.

   ### profile-editor.component.html (submit button)

   ```
   <p>Complete the form to enable button.</p>
   <button type="submit" [disabled]="!profileForm.valid">Submit</button>
   ```

   The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](#validating-form-input) section.

4) ### [Display the component](#display-the-component-form-group)

   To display the `ProfileEditor` component that contains the form, add it to a component template.

   ### app.component.html (profile editor)

   ```
   <app-profile-editor />
   ```

   `ProfileEditor` lets you manage the form control instances for the `firstName` and `lastName` controls within the form group instance.

### [Creating nested form groups](#creating-nested-form-groups)

Form groups can accept both individual form control instances and other form group instances as children. This makes composing complex form models easier to maintain and logically group together.

When building complex forms, managing the different areas of information is easier in smaller sections. Using a nested form group instance lets you break large forms groups into smaller, more manageable ones.

To make more complex forms, use the following steps.

1. Create a nested group.
2. Group the nested form in the template.

Some types of information naturally fall into the same group. A name and address are typical examples of such nested groups, and are used in the following examples.

1. ### [Create a nested group](#create-a-nested-group)

   To create a nested group in `profileForm`, add a nested `address` element to the form group instance.

   ### profile-editor.component.ts (nested form group)

   ```
   import {Component} from '@angular/core';
   import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';

   @Component({
     selector: 'app-profile-editor',
     templateUrl: './profile-editor.component.html',
     styleUrls: ['./profile-editor.component.css'],
     imports: [ReactiveFormsModule],
   })
   export class ProfileEditorComponent {
     profileForm = new FormGroup({
       firstName: new FormControl(''),
       lastName: new FormControl(''),
       address: new FormGroup({
         street: new FormControl(''),
         city: new FormControl(''),
         state: new FormControl(''),
         zip: new FormControl(''),
       }),
     });
   }
   ```

   In this example, `address group` combines the current `firstName` and `lastName` controls with the new `street`, `city`, `state`, and `zip` controls. Even though the `address` element in the form group is a child of the overall `profileForm` element in the form group, the same rules apply with value and status changes. Changes in status and value from the nested form group propagate to the parent form group, maintaining consistency with the overall model.

2. ### [Group the nested form in the template](#group-the-nested-form-in-the-template)

   After you update the model in the component class, update the template to connect the form group instance and its input elements. Add the `address` form group containing the `street`, `city`, `state`, and `zip` fields to the `ProfileEditor` template.

   ### profile-editor.component.html (template nested form group)

   ```
   <div formGroupName="address">
     <h2>Address</h2>

     <label for="street">Street: </label>
     <input id="street" type="text" formControlName="street" />

     <label for="city">City: </label>
     <input id="city" type="text" formControlName="city" />

     <label for="state">State: </label>
     <input id="state" type="text" formControlName="state" />

     <label for="zip">Zip Code: </label>
     <input id="zip" type="text" formControlName="zip" />
   </div>
   ```

   The `ProfileEditor` form is displayed as one group, but the model is broken down further to represent the logical grouping areas.

   Display the value for the form group instance in the component template using the `value` property and [`JsonPipe`](/api/common/JsonPipe).

### [Updating parts of the data model](#updating-parts-of-the-data-model)

When updating the value for a form group instance that contains multiple controls, you might only want to update parts of the model. This section covers how to update specific parts of a form control data model.

There are two ways to update the model value:

| Methods        | Details                                                                                                                                                               |
| :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setValue()`   | Set a new value for an individual control. The `setValue()` method strictly adheres to the structure of the form group and replaces the entire value for the control. |
| `patchValue()` | Replace any properties defined in the object that have changed in the form model.                                                                                     |

The strict checks of the `setValue()` method help catch nesting errors in complex forms, while `patchValue()` fails silently on those errors.

In `ProfileEditorComponent`, use the `updateProfile` method with the following example to update the first name and street address for the user.

### profile-editor.component.ts (patch value)

```
updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street',
    },
  });
}
```

Simulate an update by adding a button to the template to update the user profile on demand.

### profile-editor.component.html (update value)

```
<button type="button" (click)="updateProfile()">Update Profile</button>
```

When a user clicks the button, the `profileForm` model is updated with new values for `firstName` and `street`. Notice that `street` is provided in an object inside the `address` property. This is necessary because the `patchValue()` method applies the update against the model structure. `patchValue()` only updates properties that the form model defines.

## [Using the FormBuilder service to generate controls](#using-the-formbuilder-service-to-generate-controls)

Creating form control instances manually can become repetitive when dealing with multiple forms. The [`FormBuilder`](/api/forms/FormBuilder) service provides convenient methods for generating controls.

Use the following steps to take advantage of this service.

1. Import the [`FormBuilder`](/api/forms/FormBuilder) class.
2. Inject the [`FormBuilder`](/api/forms/FormBuilder) service.
3. Generate the form contents.

The following examples show how to refactor the `ProfileEditor` component to use the form builder service to create form control and form group instances.

1. ### [Import the FormBuilder class](#import-the-formbuilder-class)

   Import the [`FormBuilder`](/api/forms/FormBuilder) class from the `@angular/forms` package.

   ### profile-editor.component.ts (import)

   ```
   import {FormBuilder, ReactiveFormsModule} from '@angular/forms';
   ```

2. ### [Inject the FormBuilder service](#inject-the-formbuilder-service)

   The [`FormBuilder`](/api/forms/FormBuilder) service is an injectable provider from the reactive forms module. Use the `inject()` function to inject this dependency in your component.

   ### profile-editor.component.ts (property init)

   ```
   private formBuilder = inject(FormBuilder);
   ```

3. ### [Generate form controls](#generate-form-controls)

   The [`FormBuilder`](/api/forms/FormBuilder) service has three methods: `control()`, `group()`, and `array()`. These are factory methods for generating instances in your component classes including form controls, form groups, and form arrays. Use the `group` method to create the `profileForm` controls.

   ### profile-editor.component.ts (form builder)

   ```
   profileForm = this.formBuilder.group({
     firstName: [''],
     lastName: [''],
     address: this.formBuilder.group({
       street: [''],
       city: [''],
       state: [''],
       zip: [''],
     }),
   });
   ```

   In the preceding example, you use the `group()` method with the same object to define the properties in the model. The value for each control name is an array containing the initial value as the first item in the array.

   **TIP:** You can define the control with just the initial value, but if your controls need sync or async validation, add sync and async validators as the second and third items in the array. Compare using the form builder to creating the instances manually.

   ### profile-editor.component.ts (instances)

   ```
   profileForm = new FormGroup({
     firstName: new FormControl(''),
     lastName: new FormControl(''),
     address: new FormGroup({
       street: new FormControl(''),
       city: new FormControl(''),
       state: new FormControl(''),
       zip: new FormControl(''),
     }),
   });
   ```

   ### profile-editor.component.ts (form builder)

   ```
   profileForm = this.formBuilder.group({
     firstName: [''],
     lastName: [''],
     address: this.formBuilder.group({
       street: [''],
       city: [''],
       state: [''],
       zip: [''],
     }),
   });
   ```

## [Validating form input](#validating-form-input)

*Form validation* is used to ensure that user input is complete and correct. This section covers adding a single validator to a form control and displaying the overall form status. Form validation is covered more extensively in the [Form Validation](guide/forms/form-validation) guide.

Use the following steps to add form validation.

1. Import a validator function in your form component.
2. Add the validator to the field in the form.
3. Add logic to handle the validation status.

The most common validation is making a field required. The following example shows how to add a required validation to the `firstName` control and display the result of validation.

1. ### [Import a validator function](#import-a-validator-function)

   Reactive forms include a set of validator functions for common use cases. These functions receive a control to validate against and return an error object or a null value based on the validation check.

   Import the [`Validators`](/api/forms/Validators) class from the `@angular/forms` package.

   ### profile-editor.component.ts (import)

   ```
   import {Validators} from '@angular/forms';
   ```

2. ### [Make a field required](#make-a-field-required)

   In the `ProfileEditor` component, add the [`Validators.required`](/api/forms/Validators#required) static method as the second item in the array for the `firstName` control.

   ### profile-editor.component.ts (required validator)

   ```
   private formBuilder = inject(FormBuilder);

   profileForm = this.formBuilder.group({
     firstName: ['', Validators.required],
     lastName: [''],
     address: this.formBuilder.group({
       street: [''],
       city: [''],
       state: [''],
       zip: [''],
     }),
   });
   ```

3. ### [Display form status](#display-form-status)

   When you add a required field to the form control, its initial status is invalid. This invalid status propagates to the parent form group element, making its status invalid. Access the current status of the form group instance through its `status` property.

   Display the current status of `profileForm` using interpolation.

   ### profile-editor.component.html (display status)

   ```
   <p>Form Status: {{ profileForm.status }}</p>
   ```

   The **Submit** button is disabled because `profileForm` is invalid due to the required `firstName` form control. After you fill out the `firstName` input, the form becomes valid and the **Submit** button is enabled.

   For more on form validation, visit the [Form Validation](guide/forms/form-validation) guide.

## [Creating dynamic forms](#creating-dynamic-forms)

[`FormArray`](/api/forms/FormArray) is an alternative to [`FormGroup`](/api/forms/FormGroup) for managing any number of unnamed controls. As with form group instances, you can dynamically insert and remove controls from form array instances, and the form array instance value and validation status is calculated from its child controls. However, you don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance.

To define a dynamic form, take the following steps.

1. Import the [`FormArray`](/api/forms/FormArray) class.
2. Define a [`FormArray`](/api/forms/FormArray) control.
3. Access the [`FormArray`](/api/forms/FormArray) control with a getter method.
4. Display the form array in a template.

The following example shows you how to manage an array of *aliases* in `ProfileEditor`.

1. ### [Import the `FormArray` class](#import-the-formarray-class)

   Import the [`FormArray`](/api/forms/FormArray) class from `@angular/forms` to use for type information. The [`FormBuilder`](/api/forms/FormBuilder) service is ready to create a [`FormArray`](/api/forms/FormArray) instance.

   ### profile-editor.component.ts (import)

   ```
   import {FormArray} from '@angular/forms';
   ```

2. ### [Define a `FormArray` control](#define-a-formarray-control)

   You can initialize a form array with any number of controls, from zero to many, by defining them in an array. Add an `aliases` property to the form group instance for `profileForm` to define the form array.

   Use the [`FormBuilder.array()`](/api/forms/FormBuilder#array) method to define the array, and the [`FormBuilder.control()`](/api/forms/FormBuilder#control) method to populate the array with an initial control.

   ### profile-editor.component.ts (aliases form array)

   ```
   private formBuilder = inject(FormBuilder);

   profileForm = this.formBuilder.group({
     firstName: ['', Validators.required],
     lastName: [''],
     address: this.formBuilder.group({
       street: [''],
       city: [''],
       state: [''],
       zip: [''],
     }),
     aliases: this.formBuilder.array([this.formBuilder.control('')]),
   });
   ```

   The aliases control in the form group instance is now populated with a single control until more controls are added dynamically.

3. ### [Access the `FormArray` control](#access-the-formarray-control)

   A getter provides access to the aliases in the form array instance compared to repeating the `profileForm.get()` method to get each instance. The form array instance represents an undefined number of controls in an array. It's convenient to access a control through a getter, and this approach is straightforward to repeat for additional controls.

   Use the getter syntax to create an `aliases` class property to retrieve the alias's form array control from the parent form group.

   ### profile-editor.component.ts (aliases getter)

   ```
   get aliases() {
     return this.profileForm.get('aliases') as FormArray;
   }
   ```

   Because the returned control is of the type [`AbstractControl`](/api/forms/AbstractControl), you need to provide an explicit type to access the method syntax for the form array instance. Define a method to dynamically insert an alias control into the alias's form array. The [`FormArray.push()`](/api/forms/FormArray#push) method inserts the control as a new item in the array, and you can also pass an array of controls to FormArray.push() to register multiple controls at once.

   ### profile-editor.component.ts (add alias)

   ```
   addAlias() {
     this.aliases.push(this.formBuilder.control(''));
   }
   ```

   In the template, each control is displayed as a separate input field.

4. ### [Display the form array in the template](#display-the-form-array-in-the-template)

   To attach the aliases from your form model, you must add it to the template. Similar to the [`formGroupName`](/api/forms/NgControlStatusGroup) input provided by [`FormGroupName`](/api/forms/FormGroupName), [`formArrayName`](/api/forms/NgControlStatusGroup) binds communication from the form array instance to the template with [`FormArrayName`](/api/forms/FormArrayName).

   Add the following template HTML after the `<div>` closing the [`formGroupName`](/api/forms/NgControlStatusGroup) element.

   ### profile-editor.component.html (aliases form array template)

   ```
   <div formArrayName="aliases">
     <h2>Aliases</h2>
     <button type="button" (click)="addAlias()">+ Add another alias</button>

     @for (alias of aliases.controls; track $index; let i = $index) {
       <div>
         <!-- The repeated alias template -->
         <label for="alias-{{ i }}">Alias:</label>
         <input id="alias-{{ i }}" type="text" [formControlName]="i" />
       </div>
     }
   </div>
   ```

   The `@for` block iterates over each form control instance provided by the aliases form array instance. Because form array elements are unnamed, you assign the index to the `i` variable and pass it to each control to bind it to the [`formControlName`](/api/forms/SelectMultipleControlValueAccessor) input.

   Each time a new alias instance is added, the new form array instance is provided its control based on the index. This lets you track each individual control when calculating the status and value of the root control.

   **NOTE:** In zoneless applications, mutating a reactive forms model (for example calling [`FormArray.push()`](/api/forms/FormArray#push)) does not automatically schedule component change detection. If your template depends on structural model changes such as `aliases.controls`, make sure the component notifies Angular to run change detection, for example by bridging a forms observable to [`ChangeDetectorRef.markForCheck()`](/api/core/ChangeDetectorRef#markForCheck):

   ```
   import {ChangeDetectorRef, Component, inject} from '@angular/core';
   import {takeUntilDestroyed} from '@angular/core/rxjs-interop';

   @Component({/* ... */})
   export class ProfileEditor {
     private readonly cdr = inject(ChangeDetectorRef);

     constructor() {
       this.profileForm.valueChanges
         .pipe(takeUntilDestroyed())
         .subscribe(() => this.cdr.markForCheck());
     }
   }
   ```

5. ### [Using `FormArrayDirective` for top-level form arrays](#using-formarraydirective-for-top-level-form-arrays)

   You can bind a [`FormArray`](/api/forms/FormArray) directly to a `<form>` element by using the [`FormArrayDirective`](/api/forms/FormArrayDirective).\
   This is useful when the form does not use a top-level [`FormGroup`](/api/forms/FormGroup), and the array itself represents the full form model.

   ```
   import {Component} from '@angular/core';
   import {FormArray, FormControl} from '@angular/forms';

   @Component({
     selector: 'form-array-example',
     template: `
       <form [formArray]="form">
         @for (control of form.controls; track $index) {
           <input [formControlName]="$index" />
         }
       </form>
     `,
   })
   export class FormArrayExampleComponent {
     controls = [new FormControl('fish'), new FormControl('cat'), new FormControl('dog')];

     form = new FormArray(this.controls);
   }
   ```

   ### [Add an alias](#add-an-alias)

   Initially, the form contains one `Alias` field. To add another field, click the **Add Alias** button. You can also validate the array of aliases reported by the form model displayed by `Form Value` at the bottom of the template. Instead of a form control instance for each alias, you can compose another form group instance with additional fields. The process of defining a control for each item is the same.

## [Unified control state change events](#unified-control-state-change-events)

All form controls expose a single unified stream of **control state change events** through the `events` observable on [`AbstractControl`](/api/forms/AbstractControl) ([`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup), [`FormArray`](/api/forms/FormArray), and [`FormRecord`](/api/forms/FormRecord)). This unified stream lets you react to **value**, **status**, **pristine**, **touched**, and **reset** state changes, as well as **form-level actions** such as **submit**, allowing you to handle all updates with a single subscription instead of wiring multiple observables.

### [Event types](#event-types)

Each item emitted by `events` is an instance of a specific event class:

* **[`ValueChangeEvent`](/api/forms/ValueChangeEvent)** — when the control’s **value** changes.
* **[`StatusChangeEvent`](/api/forms/StatusChangeEvent)** — when the control’s **validation status** updates to one of the [`FormControlStatus`](/api/forms/FormControlStatus) values (`VALID`, `INVALID`, `PENDING`, or `DISABLED`).
* **[`PristineChangeEvent`](/api/forms/PristineChangeEvent)** — when the control’s **pristine/dirty** state changes.
* **[`TouchedChangeEvent`](/api/forms/TouchedChangeEvent)** — when the control’s **touched/untouched** state changes.
* **[`FormResetEvent`](/api/forms/FormResetEvent)** — when a control or form is reset, either via the `reset()` API or a native action.
* **[`FormSubmittedEvent`](/api/forms/FormSubmittedEvent)** — when the form is submitted.

All event classes extend [`ControlEvent`](/api/forms/ControlEvent) and include a `source` reference to the [`AbstractControl`](/api/forms/AbstractControl) that originated the change, which is useful in large forms.

```
import {Component} from '@angular/core';
import {
  FormControl,
  ValueChangeEvent,
  StatusChangeEvent,
  PristineChangeEvent,
  TouchedChangeEvent,
  FormResetEvent,
  FormSubmittedEvent,
  ReactiveFormsModule,
  FormGroup,
} from '@angular/forms';

@Component(/* ... */)
export class UnifiedEventsBasicComponent {
  form = new FormGroup({
    username: new FormControl(''),
  });

  constructor() {
    this.form.events.subscribe((e) => {
      if (e instanceof ValueChangeEvent) {
        console.log('Value changed to: ', e.value);
      }

      if (e instanceof StatusChangeEvent) {
        console.log('Status changed to: ', e.status);
      }

      if (e instanceof PristineChangeEvent) {
        console.log('Pristine status changed to: ', e.pristine);
      }

      if (e instanceof TouchedChangeEvent) {
        console.log('Touched status changed to: ', e.touched);
      }

      if (e instanceof FormResetEvent) {
        console.log('Form was reset');
      }

      if (e instanceof FormSubmittedEvent) {
        console.log('Form was submitted');
      }
    });
  }
}
```

### [Filtering specific events](#filtering-specific-events)

Prefer RxJS operators when you only need a subset of event types.

```
import {filter} from 'rxjs/operators';
import {StatusChangeEvent} from '@angular/forms';

control.events
  .pipe(filter((e) => e instanceof StatusChangeEvent))
  .subscribe((e) => console.log('Status:', e.status));
```

### [Unifying from multiple subscriptions](#unifying-from-multiple-subscriptions)

**Before**

```
import {combineLatest} from 'rxjs';

combineLatest([control.valueChanges, control.statusChanges]).subscribe(([value, status]) => {
  /* ... */
});
```

**After**

```
control.events.subscribe((e) => {
  // Handle ValueChangeEvent, StatusChangeEvent, etc.
});
```

**NOTE:** On value change, the emit happens right after a value of this control is updated. The value of a parent control (for example if this FormControl is a part of a FormGroup) is updated later, so accessing a value of a parent control (using the `value` property) from the callback of this event might result in getting a value that has not been updated yet. Subscribe to the `events` of the parent control instead.

## [Managing form control state](#managing-form-control-state)

Reactive forms track control state through **touched/untouched** and **pristine/dirty**. Angular updates these automatically during DOM interactions, but you can also manage them programmatically.

**[`markAsTouched`](api/forms/FormControl#markAsTouched)** — Marks a control or form as touched by focus and blur events that do not change the value. Propagates to parent controls by default.

```
// Show validation errors after user leaves a field
onEmailBlur() {
  const email = this.form.get('email');
  email.markAsTouched();
}
```

**[`markAsUntouched`](api/forms/FormControl#markAsUntouched)** — Marks a control or form as untouched. Cascades to all child controls and recalculates the touched status of all parent controls.

```
// Reset form state after successful submission
onSubmitSuccess() {
  this.form.markAsUntouched();
  this.form.markAsPristine();
}
```

**[`markAsDirty`](api/forms/FormControl#markAsDirty)** — Marks a control or form as dirty, meaning the value has been changed. Propagates to parent controls by default.

```
// Mark programmatically changed values as modified
autofillAddress() {
  const previousAddress = getAddress();
  this.form.patchValue(previousAddress, { emitEvent: false });
  this.form.markAsDirty();
}
```

**[`markAsPristine`](api/forms/FormControl#markAsPristine)** — Marks a control or form as pristine. Marks all child controls as pristine and recalculates the pristine status of all parent controls.

```
// Reset pristine state after saving to track new changes
saveForm() {
  this.api.save(this.form.value).subscribe(() => {
    this.form.markAsPristine();
  });
}
```

**[`markAllAsDirty`](api/forms/FormControl#markAllAsDirty)** — Marks the control or form and all its descendant controls as dirty.

```
// Mark imported data as dirty
loadData(data: FormData) {
  this.form.patchValue(data);
  this.form.markAllAsDirty();
}
```

**[`markAllAsTouched`](api/forms/FormControl#markAllAsTouched)** — Marks the control or form and all its descendant controls as touched. Useful for showing validation errors across the entire form.

```
// Show all validation errors before submission
onSubmit() {
  if (this.form.invalid) {
    this.form.markAllAsTouched();
    return;
  }
  this.saveForm();
}
```

## [Controlling event emission and propagation](#controlling-event-emission-and-propagation)

When updating form controls programmatically, you have precise control over how changes propagate through the form hierarchy and whether events are emitted.

### [Understanding event emission](#understanding-event-emission)

By default `emitEvent: true`, any change to a control emits events through the `valueChanges` and `statusChanges` observables. Setting `emitEvent: false` suppresses these emissions, which is useful when setting values programmatically without triggering reactive behavior like auto-save, avoiding circular updates between controls, or performing bulk updates where events should emit only once at the end.

```
@Component({/* ... */})
export class BlogPostEditor {
  postForm = new FormGroup({
    title: new FormControl(''),
    content: new FormControl(''),
  });

  constructor() {
    // Auto-save draft every time user types
    this.postForm.valueChanges.subscribe((formValue) => {
      this.autosaveDraft(formValue);
    });
  }

  loadExistingDraft(savedDraft: {title: string; content: string}) {
    // Restore draft without triggering auto-save
    this.postForm.setValue(savedDraft, {emitEvent: false});
  }
}
```

### [Understanding propagation control](#understanding-propagation-control)

By default `onlySelf: false`, updates cascade to parent controls, recalculating their values and validation status. Setting `onlySelf: true` isolates the update to the current control, preventing parent notification. This is useful for batch operations where you want to manually trigger the parent update once.

```
updatePostalCodeValidator(country: string) {
  const postal = this.addressForm.get('postalCode');

  const validators = country === 'US'
    ? [Validators.maxLength(5)]
    : [Validators.maxLength(7)];

  postal.setValidators(validators);
  postal.updateValueAndValidity({ onlySelf: true, emitEvent: false });
}
```

**HELPFUL:** For dynamically managing validators at runtime, see the [Managing validators dynamically in reactive forms](guide/forms/form-validation#managing-validators-dynamically-in-reactive-forms) section in the Form Validation guide.

## [Utility functions for narrowing form control types](#utility-functions-for-narrowing-form-control-types)

Angular provides four utility functions that help determine the concrete type of an [`AbstractControl`](/api/forms/AbstractControl). These functions act as **type guards** and narrow the control type when they return `true`, which lets you safely access subtype-specific properties inside the same block.

| Utility function                            | Details                                                                       |
| :------------------------------------------ | :---------------------------------------------------------------------------- |
| [`isFormControl`](/api/forms/isFormControl) | Returns `true` when the control is a [`FormControl`](/api/forms/FormControl). |
| [`isFormGroup`](/api/forms/isFormGroup)     | Returns `true` when the control is a [`FormGroup`](/api/forms/FormGroup)      |
| [`isFormRecord`](/api/forms/isFormRecord)   | Returns `true` when the control is a [`FormRecord`](/api/forms/FormRecord)    |
| [`isFormArray`](/api/forms/isFormArray)     | Returns `true` when the control is a [`FormArray`](/api/forms/FormArray)      |

These helpers are particularly useful in **custom validators**, where the function signature receives an [`AbstractControl`](/api/forms/AbstractControl), but the logic is intended for a specific control kind.

```
import {AbstractControl, isFormArray} from '@angular/forms';

export function positiveValues(control: AbstractControl) {
  if (!isFormArray(control)) {
    return null; // Not a FormArray: validator is not applicable.
  }

  // Safe to access FormArray-specific API after narrowing.
  const hasNegative = control.controls.some((c) => c.value < 0);
  return hasNegative ? {positiveValues: true} : null;
}
```

## [Reactive forms API summary](#reactive-forms-api-summary)

The following table lists the base classes and services used to create and manage reactive form controls. For complete syntax details, see the API reference documentation for the [Forms package](api#angular_forms "API reference").

### [Classes](#classes)

| Class                                           | Details                                                                                                                                                                                                                                       |
| :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`AbstractControl`](/api/forms/AbstractControl) | The abstract base class for the concrete form control classes [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup), and [`FormArray`](/api/forms/FormArray). It provides their common behaviors and properties.       |
| [`FormControl`](/api/forms/FormControl)         | Manages the value and validity status of an individual form control. It corresponds to an HTML form control such as `<input>` or `<select>`.                                                                                                  |
| [`FormGroup`](/api/forms/FormGroup)             | Manages the value and validity state of a group of [`AbstractControl`](/api/forms/AbstractControl) instances. The group's properties include its child controls. The top-level form in your component is [`FormGroup`](/api/forms/FormGroup). |
| [`FormArray`](/api/forms/FormArray)             | Manages the value and validity state of a numerically indexed array of [`AbstractControl`](/api/forms/AbstractControl) instances.                                                                                                             |
| [`FormBuilder`](/api/forms/FormBuilder)         | An injectable service that provides factory methods for creating control instances.                                                                                                                                                           |
| [`FormRecord`](/api/forms/FormRecord)           | Tracks the value and validity state of a collection of [`FormControl`](/api/forms/FormControl) instances, each of which has the same value type.                                                                                              |

### [Directives](#directives)

| Directive                                                 | Details                                                                                                                                      |
| :-------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
| [`FormControlDirective`](/api/forms/FormControlDirective) | Syncs a standalone [`FormControl`](/api/forms/FormControl) instance to a form control element.                                               |
| [`FormControlName`](/api/forms/FormControlName)           | Syncs [`FormControl`](/api/forms/FormControl) in an existing [`FormGroup`](/api/forms/FormGroup) instance to a form control element by name. |
| [`FormGroupDirective`](/api/forms/FormGroupDirective)     | Syncs an existing [`FormGroup`](/api/forms/FormGroup) instance to a DOM element.                                                             |
| [`FormGroupName`](/api/forms/FormGroupName)               | Syncs a nested [`FormGroup`](/api/forms/FormGroup) instance to a DOM element.                                                                |
| [`FormArrayName`](/api/forms/FormArrayName)               | Syncs a nested [`FormArray`](/api/forms/FormArray) instance to a DOM element.                                                                |
| [`FormArrayDirective`](/api/forms/FormArrayDirective)     | Syncs a standalone [`FormArray`](/api/forms/FormArray) instance to a DOM element.                                                            |

----
url: https://angular.dev/guide/routing/router-reference
----

The following sections highlight some core router concepts and terminology.

## [Router events](#router-events)

During each navigation, the [`Router`](/api/router/Router) emits navigation events through the [`Router.events`](/api/router/Router#events) property. These events are shown in the following table.

| Router event                                              | Details                                                                                                                                                                                                                      |
| :-------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`NavigationStart`](api/router/NavigationStart)           | Triggered when navigation starts.                                                                                                                                                                                            |
| [`RouteConfigLoadStart`](api/router/RouteConfigLoadStart) | Triggered before the [`Router`](/api/router/Router) lazy loads a route configuration.                                                                                                                                        |
| [`RouteConfigLoadEnd`](api/router/RouteConfigLoadEnd)     | Triggered after a route has been lazy loaded.                                                                                                                                                                                |
| [`RoutesRecognized`](api/router/RoutesRecognized)         | Triggered when the Router parses the URL and the routes are recognized.                                                                                                                                                      |
| [`GuardsCheckStart`](api/router/GuardsCheckStart)         | Triggered when the Router begins the Guards phase of routing.                                                                                                                                                                |
| [`ChildActivationStart`](api/router/ChildActivationStart) | Triggered when the Router begins activating a route's children.                                                                                                                                                              |
| [`ActivationStart`](api/router/ActivationStart)           | Triggered when the Router begins activating a route.                                                                                                                                                                         |
| [`GuardsCheckEnd`](api/router/GuardsCheckEnd)             | Triggered when the Router finishes the Guards phase of routing successfully.                                                                                                                                                 |
| [`ResolveStart`](api/router/ResolveStart)                 | Triggered when the Router begins the Resolve phase of routing.                                                                                                                                                               |
| [`ResolveEnd`](api/router/ResolveEnd)                     | Triggered when the Router finishes the Resolve phase of routing successfully.                                                                                                                                                |
| [`ChildActivationEnd`](api/router/ChildActivationEnd)     | Triggered when the Router finishes activating a route's children.                                                                                                                                                            |
| [`ActivationEnd`](api/router/ActivationEnd)               | Triggered when the Router finishes activating a route.                                                                                                                                                                       |
| [`NavigationEnd`](api/router/NavigationEnd)               | Triggered when navigation ends successfully.                                                                                                                                                                                 |
| [`NavigationCancel`](api/router/NavigationCancel)         | Triggered when navigation is canceled. This can happen when a Route Guard returns false during navigation, or redirects by returning a [`UrlTree`](/api/router/UrlTree) or [`RedirectCommand`](/api/router/RedirectCommand). |
| [`NavigationError`](api/router/NavigationError)           | Triggered when navigation fails due to an unexpected error.                                                                                                                                                                  |
| [`Scroll`](api/router/Scroll)                             | Represents a scrolling event.                                                                                                                                                                                                |

When you enable the [`withDebugTracing`](/api/router/withDebugTracing) feature, Angular logs these events to the console.

## [Router terminology](#router-terminology)

Here are the key [`Router`](/api/router/Router) terms and their meanings:

| Router part                                        | Details                                                                                                                                                                                                                                                                     |
| :------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`Router`](/api/router/Router)                     | Displays the application component for the active URL. Manages navigation from one component to the next.                                                                                                                                                                   |
| [`provideRouter`](/api/router/provideRouter)       | provides the necessary service providers for navigating through application views.                                                                                                                                                                                          |
| [`RouterModule`](/api/router/RouterModule)         | A separate NgModule that provides the necessary service providers and directives for navigating through application views.                                                                                                                                                  |
| [`Routes`](/api/router/Routes)                     | Defines an array of Routes, each mapping a URL path to a component.                                                                                                                                                                                                         |
| [`Route`](/api/router/Route)                       | Defines how the router should navigate to a component based on a URL pattern. Routes can include paths, components, child routes, guards, resolvers, and resources.                                                                                                         |
| [`RouterOutlet`](/api/router/RouterOutlet)         | The directive (`<router-outlet>`) that marks where the router displays a view.                                                                                                                                                                                              |
| [`RouterLink`](/api/router/RouterLink)             | The directive for binding a clickable HTML element to a route. Clicking an element with a [`routerLink`](/api/router/RouterLinkWithHref) directive that's bound to a *string* or a *link parameters array* triggers a navigation.                                           |
| [`RouterLinkActive`](/api/router/RouterLinkActive) | The directive for adding/removing classes from an HTML element when an associated [`routerLink`](/api/router/RouterLinkWithHref) contained on or inside the element becomes active/inactive. It can also set the `aria-current` of an active link for better accessibility. |
| [`ActivatedRoute`](/api/router/ActivatedRoute)     | A service that's provided to each route component that contains route specific information such as route parameters, static data, resolve data, route resources, global query parameters, and the global fragment.                                                          |
| [`RouterState`](/api/router/RouterState)           | The current state of the router including a tree of the currently activated routes together with convenience methods for traversing the route tree.                                                                                                                         |
| Link parameters array                              | An array that the router interprets as a routing instruction. You can bind that array to a [`RouterLink`](/api/router/RouterLink) or pass the array as an argument to the [`Router.navigate`](/api/router/Router#navigate) method.                                          |
| Routing component                                  | An Angular component with a [`RouterOutlet`](/api/router/RouterOutlet) that displays views based on router navigations.                                                                                                                                                     |

## [`<base href>`](#base-href)

The router uses the browser's [history.pushState](https://developer.mozilla.org/docs/Web/API/History_API/Working_with_the_History_API#adding_and_modifying_history_entries "HTML5 browser history push-state") for navigation. `pushState` lets you customize in-application URL paths; for example, `localhost:4200/crisis-center`. The in-application URLs can be indistinguishable from server URLs.

Modern HTML5 browsers were the first to support `pushState` which is why many people refer to these URLs as "HTML5 style" URLs.

**HELPFUL:** HTML5 style navigation is the router default. In the [LocationStrategy and browser URL styles](guide/routing/common-router-tasks#locationstrategy-and-browser-url-styles) section, learn why HTML5 style is preferable, how to adjust its behavior, and how to switch to the older hash (`#`) style, if necessary.

You must add a [`<base href>` element](https://developer.mozilla.org/docs/Web/HTML/Element/base "base href") to the application's `index.html` for `pushState` routing to work. The browser uses the `<base href>` value to prefix relative URLs when referencing CSS files, scripts, and images.

Add the `<base>` element just after the `<head>` tag. If the `app` folder is the application root, as it is for this application, set the `href` value in `index.html` as shown here.

```
<base href="/" />
```

### [HTML5 URLs and the `<base href>`](#html5-urls-and-the-base-href)

The guidelines that follow will refer to different parts of a URL. This diagram outlines what those parts refer to:

```
foo://example.com:8042/over/there?name=ferret#nose
\_/   \______________/\_________/ \_________/ \__/
 |           |            |            |        |
scheme    authority      path        query   fragment
```

While the router uses the [HTML5 pushState](https://developer.mozilla.org/docs/Web/API/History_API#Adding_and_modifying_history_entries "Browser history push-state") style by default, you must configure that strategy with a `<base href>`.

The preferred way to configure the strategy is to add a [`<base href>` element](https://developer.mozilla.org/docs/Web/HTML/Element/base "base href") tag in the `<head>` of the `index.html`.

```
<base href="/" />
```

Without that tag, the browser might not be able to load resources (images, CSS, scripts) when "deep linking" into the application.

Some developers might not be able to add the `<base>` element, perhaps because they don't have access to `<head>` or the `index.html`.

Those developers can still use HTML5 URLs by taking the following two steps:

1. Provide the router with an appropriate [`APP_BASE_HREF`](/api/common/APP_BASE_HREF) value.

2. Use root URLs (URLs with an `authority`) for all web resources: CSS, images, scripts, and template HTML files.

   * The `<base href>` `path` should end with a "/", as browsers ignore characters in the `path` that follow the right-most "`/`"

   * If the `<base href>` includes a `query` part, the `query` is only used if the `path` of a link in the page is empty and has no `query`. This means that a `query` in the `<base href>` is only included when using [`HashLocationStrategy`](/api/common/HashLocationStrategy).

   * If a link in the page is a root URL (has an `authority`), the `<base href>` is not used. In this way, an [`APP_BASE_HREF`](/api/common/APP_BASE_HREF) with an authority will cause all links created by Angular to ignore the `<base href>` value.

   * A fragment in the `<base href>` is *never* persisted

For more complete information on how `<base href>` is used to construct target URIs, see the [RFC](https://tools.ietf.org/html/rfc3986#section-5.2.2) section on transforming references.

### [`HashLocationStrategy`](#hashlocationstrategy)

Use [`HashLocationStrategy`](/api/common/HashLocationStrategy) by providing the `useHash: true` in an object as the second argument of the [`RouterModule.forRoot()`](/api/router/RouterModule#forRoot) in the `AppModule`.

```
providers: [provideRouter(appRoutes, withHashLocation())];
```

When using [`RouterModule.forRoot`](/api/router/RouterModule#forRoot), this is configured with the `useHash: true` in the second argument: [`RouterModule.forRoot(routes, {useHash: true})`](/api/router/RouterModule#forRoot).

----
url: https://angular.dev/api/cdk/drag-drop/DropListRef
----

```
    
      class DropListRef<T = any> {}
    
    
```

@paramelement`any`

@param\_dragDropRegistry`DragDropRegistry`

@param\_document`any`

@param\_ngZone`NgZone`

@param\_viewportRuler`ViewportRuler`

@returns`DropListRef<T>`

Element that the drop list is attached to.

Whether starting a dragging sequence from this container is disabled.

Whether sorting items within the list is disabled.

Locks the position of the draggable elements inside the container along the specified axis.

Whether auto-scrolling the view when the user moves their pointer close to the edges is disabled.

Number of pixels to scroll for each frame when auto-scrolling an element.

Whether the items in the list should leave an anchor node when leaving the initial container.

Function that is used to determine whether an item is allowed to be moved into a drop container.

Function that is used to determine whether an item can be sorted into a particular index.

Emits right before dragging has started.

Emits when the user has moved a new drag item into this container.

Emits when the user removes an item from the container by dragging it into another container.

Emits when the user drops an item inside the container.

Emits as the user is swapping items while actively dragging.

Emits when a dragging sequence is started in a list connected to the current one.

Emits when a dragging sequence is stopped from a list connected to the current one.

Arbitrary data that can be attached to the drop list.

Removes the drop list functionality from the DOM element.

@returns`void`

Whether an item from this list is currently being dragged.

@returns`boolean`

Starts dragging an item.

@returns`void`

Attempts to move an item into the container.

@paramitem`DragRef<any>`

Item that was moved into the container.

@parampointerX`number`

Position of the item along the X axis.

@parampointerY`number`

Position of the item along the Y axis.

@paramindex`number | undefined`

Index at which the item entered. If omitted, the container will try to figure it out automatically.

@returns`void`

Removes an item from the container after it was dragged into another container by the user.

@paramitem`DragRef<any>`

Item that was dragged out.

@returns`void`

Drops an item into this container.

@paramitem`DragRef<any>`

Item being dropped into the container.

@paramcurrentIndex`number`

Index at which the item should be inserted.

@parampreviousIndex`number`

Index of the item when dragging started.

@parampreviousContainer`DropListRef<any>`

Container from which the item got dragged in.

@paramisPointerOverContainer`boolean`

Whether the user's pointer was over the container when the item was dropped.

@paramdistance`Point`

Distance the user has dragged since the start of the dragging sequence.

@paramdropPoint`Point`

@paramevent`MouseEvent | TouchEvent`

Event that triggered the dropping sequence.

@returns`void`

Sets the draggable items that are a part of this list.

@paramitems`DragRef<any>[]`

Items that are a part of this list.

@returns`this`

Sets the layout direction of the drop list.

@paramdirection`Direction`

@returns`this`

Sets the containers that are connected to this one. When two or more containers are connected, the user will be allowed to transfer items between them.

@paramconnectedTo`DropListRef<any>[]`

Other containers that the current containers should be connected to.

@returns`this`

Sets the orientation of the container.

@paramorientation`DropListOrientation`

New orientation for the container.

@returns`this`

Sets which parent elements are can be scrolled while the user is dragging.

@paramelements`HTMLElement[]`

Elements that can be scrolled.

@returns`this`

Configures the drop list so that a different element is used as the container for the dragged items. This is useful for the cases when one might not have control over the full DOM that sets up the dragging. Note that the alternate container needs to be a descendant of the drop list.

@paramcontainer`HTMLElement`

New element container to be assigned.

@returns`this`

Gets the scrollable parents that are registered with this drop container.

@returns`readonly HTMLElement[]`

Figures out the index of an item in the container.

@paramitem`DragRef<any>`

Item whose index should be determined.

@returns`number`

Gets the item at a specific index.

@paramindex`number`

Index at which to retrieve the item.

@returns`DragRef<any> | null`

Whether the list is able to receive the item that is currently being dragged inside a connected drop list.

@returns`boolean`

Jump to details

----
url: https://angular.dev/api/platform-browser/SafeStyle
----

```
    
      interface SafeStyle extends SafeValue {}
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG05104
----

Bootstrapped components are defined in the `bootstrap` property of an [`@NgModule`](/api/core/NgModule) decorator or as the first parameter of [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) for standalone components.

This error happens when Angular tries to bootstrap one of these components but cannot find its corresponding node in the DOM.

## [Debugging the error](#debugging-the-error)

This issue occurs when the selector mismatches the tag

```
@Component({
  selector: 'my-app',
  ...
})
export class App {}
```

```
<html>
  <app-root></app-root> <!-- Doesn't match the selector -->
</html>
```

Replace the tag with the correct one:

```
<html>
  <my-app></my-app> <!-- OK -->
</html>
```

----
url: https://angular.dev/guide/aria/select
----

A pattern that combines a combobox with a listbox to create single-selection dropdowns with keyboard navigation and screen reader support.

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-basic"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  cursor: pointer;
  padding: 0 3.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-material"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  cursor: pointer;
  height: 3rem;
  padding: 0 3.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-retro"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  cursor: pointer;
  padding: 0 4.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

## [Usage](#usage)

The select pattern works best when users need to choose a single value from a familiar set of options.

Consider using this pattern when:

* **The option list is fixed** (fewer than 20 items) - Users can scan and choose without filtering
* **Options are familiar** - Users recognize the choices without needing to search
* **Forms need standard fields** - Country, state, category, or status selection
* **Settings and configuration** - Dropdown menus for preferences or options
* **Clear option labels** - Each choice has a distinct, scannable name

Avoid this pattern when:

* **The list has more than 20 items** - Use the [Autocomplete pattern](guide/aria/autocomplete) for better filtering
* **Users need to search options** - [Autocomplete](guide/aria/autocomplete) provides text input and filtering
* **Multiple selection is needed** - Use the [Multiselect pattern](guide/aria/multiselect) instead
* **Very few options exist (2-3)** - Radio buttons provide better visibility of all choices

## [Features](#features)

The select pattern combines [Combobox](guide/aria/combobox) and [Listbox](guide/aria/listbox) directives to provide a fully accessible dropdown with:

* **Keyboard Navigation** - Navigate options with arrow keys, select with Enter, close with Escape
* **Screen Reader Support** - Built-in ARIA attributes for assistive technologies
* **Custom Display** - Show selected values with icons, formatting, or rich content
* **Signal-Based Reactivity** - Reactive state management using Angular signals
* **Smart Positioning** - CDK Overlay handles viewport edges and scrolling
* **Bidirectional Text Support** - Automatically handles right-to-left (RTL) languages

## [Examples](#examples)

### [Basic select](#basic-select)

Users need a standard dropdown to choose from a list of values. A combobox paired with a listbox provides the familiar select experience with full accessibility support.

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="basic-basic"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');
  readonly popupExpanded = signal(false);

  readonly labels = [
    'Important',
    'Starred',
    'Work',
    'Personal',
    'To Do',
    'Later',
    'Read',
    'Travel',
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label) {
          <div ngOption [value]="label" [label]="label">
            <span class="example-option-text">{{ label }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  cursor: pointer;
  padding: 0 3rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 2rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="basic-material"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');
  readonly popupExpanded = signal(false);

  readonly labels = [
    'Important',
    'Starred',
    'Work',
    'Personal',
    'To Do',
    'Later',
    'Read',
    'Travel',
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label) {
          <div ngOption [value]="label" [label]="label">
            <span class="example-option-text">{{ label }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  cursor: pointer;
  height: 3rem;
  padding: 0 3rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 2rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="basic-retro"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');
  readonly popupExpanded = signal(false);

  readonly labels = [
    'Important',
    'Starred',
    'Work',
    'Personal',
    'To Do',
    'Later',
    'Read',
    'Travel',
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label) {
          <div ngOption [value]="label" [label]="label">
            <span class="example-option-text">{{ label }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  cursor: pointer;
  padding: 0 4.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

Text input is prevented by applying the [`ngCombobox`](/api/aria/combobox/Combobox) directive directly onto a non-interactive host element (such as a `div` or a `button`) instead of an `<input>`. Users interact with the dropdown using arrow keys and Enter, just like a native select element.

### [Select with custom display](#select-with-custom-display)

Options often need visual indicators like icons or badges to help users identify choices quickly. Custom templates within options allow rich formatting while maintaining accessibility.

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-basic"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  cursor: pointer;
  padding: 0 3.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-material"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  cursor: pointer;
  height: 3rem;
  padding: 0 3.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="icons-retro"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly popupExpanded = signal(false);

  readonly displayIcon = computed(() => {
    const val = this.selectedValues()[0];
    const label = this.labels.find((label) => label.value === val);
    return label ? label.icon : '';
  });

  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');

  readonly labels = [
    {value: 'Important', icon: 'label'},
    {value: 'Starred', icon: 'star'},
    {value: 'Work', icon: 'work'},
    {value: 'Personal', icon: 'person'},
    {value: 'To Do', icon: 'checklist'},
    {value: 'Later', icon: 'schedule'},
    {value: 'Read', icon: 'menu_book'},
    {value: 'Travel', icon: 'flight'},
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
>
  <span class="combobox-label">
    <span class="selected-label-icon material-symbols-outlined" translate="no" aria-hidden="true">{{
      displayIcon()
    }}</span>
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label.value) {
          <div ngOption [value]="label.value" [label]="label.value">
            <span
              class="example-option-icon material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >{{ label.icon }}</span
            >
            <span class="example-option-text">{{ label.value }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  cursor: pointer;
  padding: 0 4.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:hover,
.select:focus,
.select:focus-within {
  transform: translate(1px, 1px);
}

.select:active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

Each option displays an icon alongside the label. The selected value updates to show the chosen option's icon and text, providing clear visual feedback.

### [Disabled select](#disabled-select)

Selects can be disabled to prevent user interaction when certain form conditions aren't met. The disabled state provides visual feedback and prevents keyboard interaction.

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="disabled-basic"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');
  readonly popupExpanded = signal(false);

  readonly labels = [
    'Important',
    'Starred',
    'Work',
    'Personal',
    'To Do',
    'Later',
    'Read',
    'Travel',
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
  disabled
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label) {
          <div ngOption [value]="label" [label]="label">
            <span class="example-option-text">{{ label }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent);
  cursor: pointer;
  padding: 0 3rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:not([aria-disabled='true']):hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

.combobox-label {
  gap: 1rem;
  left: 2rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="disabled-material"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');
  readonly popupExpanded = signal(false);

  readonly labels = [
    'Important',
    'Starred',
    'Work',
    'Personal',
    'To Do',
    'Later',
    'Read',
    'Travel',
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
  disabled
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label) {
          <div ngOption [value]="label" [label]="label">
            <span class="example-option-text">{{ label }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  border-radius: 3rem;
  color: var(--on-primary);
  background-color: var(--primary);
  border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent);
  cursor: pointer;
  height: 3rem;
  padding: 0 3rem;
  box-sizing: border-box;
  width: 14rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:not([aria-disabled='true']):hover {
  background-color: color-mix(in srgb, var(--primary) 90%, transparent);
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.select:focus,
.select:focus-within {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.combobox-label {
  gap: 1rem;
  left: 2rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 8px;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  max-height: 13rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 13rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root[theme="disabled-retro"], app-root:not([theme])',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class App {
  readonly listbox = viewChild(Listbox);

  readonly selectedValues = signal<string[]>([]);
  readonly displayValue = computed(() => this.selectedValues()[0] || 'Select a label');
  readonly popupExpanded = signal(false);

  readonly labels = [
    'Important',
    'Starred',
    'Work',
    'Personal',
    'To Do',
    'Later',
    'Read',
    'Travel',
  ];

  constructor() {
    afterRenderEffect(() => {
      this.listbox()?.scrollActiveItemIntoView();
    });
  }

  onCommit() {
    this.popupExpanded.set(false);
  }
}
```

### app.html

```
<div
  ngCombobox
  #combobox="ngCombobox"
  [(expanded)]="popupExpanded"
  [preserveContent]="true"
  class="select"
  disabled
>
  <span class="combobox-label">
    <span class="selected-label-text">{{ displayValue() }}</span>
  </span>
  <span class="example-arrow material-symbols-outlined" translate="no" aria-hidden="true"
    >arrow_drop_down</span
  >
</div>

<ng-template
  [cdkConnectedOverlay]="{origin: combobox.element, usePopover: 'inline', matchWidth: true}"
  [cdkConnectedOverlayOpen]="popupExpanded()"
>
  <ng-template ngComboboxPopup [combobox]="combobox">
    <div class="example-popup-container">
      <div
        #listbox="ngListbox"
        ngListbox
        ngComboboxWidget
        [tabindex]="-1"
        focusMode="activedescendant"
        selectionMode="explicit"
        [(value)]="selectedValues"
        [activeDescendant]="listbox.activeDescendant()"
        (click)="onCommit()"
        (keydown.enter)="onCommit()"
        (keydown.space)="onCommit()"
      >
        @for (label of labels; track label) {
          <div ngOption [value]="label" [label]="label">
            <span class="example-option-text">{{ label }}</span>
            <span
              class="example-option-check material-symbols-outlined"
              translate="no"
              aria-hidden="true"
              >check</span
            >
          </div>
        }
      </div>
    </div>
  </ng-template>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

.select,
[ngListbox],
[ngOption] {
  outline: none;
}

.select {
  display: flex;
  position: relative;
  align-items: center;
  color: var(--page-background);
  background-color: var(--hot-pink);
  box-shadow: var(--retro-clickable-shadow);
  cursor: pointer;
  padding: 0 4.5rem;
  height: 2.5rem;
  box-sizing: border-box;
  width: 16rem;
  user-select: none;
  -webkit-user-select: none;
}

.select span {
  user-select: none;
  -webkit-user-select: none;
}

.select:not([aria-disabled='true']):hover,
.select:not([aria-disabled='true']):focus,
.select:not([aria-disabled='true']):focus-within {
  transform: translate(1px, 1px);
}

.select:not([aria-disabled='true']):active {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

.select[aria-disabled='true'] {
  opacity: 0.6;
  cursor: default;
}

.selected-label-icon {
  font-size: 1.25rem;
}

.select[aria-expanded='false']:focus,
.select[aria-expanded='false']:focus-within {
  outline-offset: 8px;
  outline: 4px dashed var(--hot-pink);
}

.combobox-label {
  gap: 1rem;
  left: 1rem;
  display: flex;
  position: absolute;
  align-items: center;
  pointer-events: none;
}

.example-arrow {
  right: 1rem;
  position: absolute;
  pointer-events: none;
  transition: transform 150ms ease-in-out;
}

.select[aria-expanded='true'] .example-arrow {
  transform: rotate(180deg);
}

.example-popup-container {
  width: 100%;
  padding: 0.5rem;
  margin-top: 20px;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
  max-height: 11rem;
  box-sizing: border-box;
  overflow: hidden;
  animation: smoothPopupOpen 150ms ease-out forwards;
  transform-origin: top;
}

@keyframes smoothPopupOpen {
  0% {
    max-height: 0;
    opacity: 0;
  }
  16.7% {
    opacity: 1;
  }
  100% {
    max-height: 11rem;
    opacity: 1;
  }
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.example-option-icon {
  font-size: 1.25rem;
  padding-right: 1rem;
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

When disabled, the select shows a disabled visual state and blocks all user interaction. Screen readers announce the disabled state to assistive technology users.

## [Testing](#testing)

The select pattern can be tested using a combination of `ComboboxHarness` and `ListboxHarness` from `@angular/aria/combobox/testing` and `@angular/aria/listbox/testing`. Here is an example of how to use the harnesses to test a select component:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComboboxHarness} from '@angular/aria/combobox/testing';
import {ListboxHarness} from '@angular/aria/listbox/testing';
import {MySelectComponent} from './my-select'; // Your component

describe('MySelectComponent', () => {
  let fixture: ComponentFixture<MySelectComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MySelectComponent],
    });

    fixture = TestBed.createComponent(MySelectComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should allow selecting an option', async () => {
    // Load the combobox harness (which acts as the select trigger)
    const select = await loader.getHarness(ComboboxHarness);

    // Verify it is closed initially
    expect(await select.isOpen()).toBe(false);

    // Open the dropdown
    await select.open();
    expect(await select.isOpen()).toBe(true);

    // Get the listbox harness from the popup
    const listbox = await select.getPopupWidget(ListboxHarness);
    const options = await listbox.getOptions();
    expect(options.length).toBe(3);

    // Click the second option
    await options[1].click();

    // Verify the dropdown closed and the value updated
    expect(await select.isOpen()).toBe(false);
    expect(await (await select.host()).text()).toContain('Option 2');
  });
});
```

### [Positioning](#positioning)

The select pattern integrates with [CDK Overlay](https://material.angular.dev/cdk/overlay/overview) for smart positioning. Use `cdkConnectedOverlay` to handle viewport edges and scrolling automatically.

----
url: https://angular.dev/api/platform-browser/EVENT_MANAGER_PLUGINS
----

```
    
      const EVENT_MANAGER_PLUGINS: InjectionToken<EventManagerPlugin[]>;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/CanMatchFn
----

```
    
      type CanMatchFn = (  route: Route,  segments: UrlSegment[],  currentSnapshot: PartialMatchRouteSnapshot,) => MaybeAsync<GuardResult>
    
    
```

## [Description](#description)

The signature of a function used as a `canMatch` guard on a [`Route`](/api/router/Route).

If all guards return `true`, navigation continues and the [`Router`](/api/router/Router) will use the [`Route`](/api/router/Route) during activation. If any guard returns `false`, the [`Route`](/api/router/Route) is skipped for matching and other [`Route`](/api/router/Route) configurations are processed instead.

The following example implements and uses a [`CanMatchFn`](/api/router/CanMatchFn) that checks whether the current user has permission to access the team page.

```
const canMatchTeam: CanMatchFn = (
  route: Route,
  segments: UrlSegment[],
  currentSnapshot: PartialMatchRouteSnapshot,
) => {
  return inject(PermissionsService).canMatch(inject(UserToken));
};

bootstrapApplication(App, {
  providers: [
    provideRouter([
      {
        path: 'team/:id',
        component: TeamComponent,
        canMatch: [canMatchTeam],
      },
    ]),
  ],
});
```

Jump to details

----
url: https://angular.dev/errors/NG0750
----

This error occurs when loading dependencies for a `@defer` block fails (typically due to poor network conditions) and no `@error` block has been configured to handle the failure state. Having no `@error` block in this scenario may create a poor user experience.

## [Debugging the error](#debugging-the-error)

Verify that you added `@error` blocks to your `@defer` blocks to handle failure states.

----
url: https://angular.dev/guide/routing
----

In-depth Guides

Routing

# Angular Routing

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/routing/overview.md "Edit this page")

Routing helps you change what the user sees in a single-page app.

Angular Router (`@angular/router`) is the official library for managing navigation in Angular applications and a core part of the framework. It is included by default in all projects created by Angular CLI.

## [Why is routing necessary in a SPA?](#why-is-routing-necessary-in-a-spa)

When you navigate to a URL in your web browser, the browser normally makes a network request to a web server and displays the returned HTML page. When you navigate to a different URL, such as clicking a link, the browser makes another network request and replaces the entire page with a new one.

A single-page application (SPA) differs in that the browser only makes a request to a web server for the first page, the `index.html`. After that, a client-side router takes over, controlling which content displays based on the URL. When a user navigates to a different URL, the router updates the page's content in place without triggering a full-page reload.

## [How Angular manages routing](#how-angular-manages-routing)

Routing in Angular is comprised of three primary parts:

1. **Routes** define which component displays when a user visits a specific URL.
2. **Outlets** are placeholders in your templates that dynamically load and render components based on the active route.
3. **Links** provide a way for users to navigate between different routes in your application without triggering a full page reload.

In addition, the Angular Routing library offers additional functionality such as:

* Nested routes
* Programmatic navigation
* Route params, queries and wildcards
* Activated route information with [`ActivatedRoute`](/api/router/ActivatedRoute)
* Data fetching with route resources and resolvers
* View transition effects
* Navigation guards

## [Next steps](#next-steps)

Learn about how you can [define routes using Angular router](/guide/routing/define-routes).

----
url: https://angular.dev/tutorials
----

Welcome to the Angular tutorials! These tutorials will guide you through the core concepts of the framework, and get you started building performant, scalable apps.

### [Learn Angular in your browser](tutorials/learn-angular)

[via the Playground](tutorials/learn-angular)

[Start coding](tutorials/learn-angular)

### [Angular AI Tutor](ai/ai-tutor)

[via MCP Server](ai/ai-tutor)

[Installation and usage instructions](ai/ai-tutor)

### [Build your first Angular app locally](tutorials/first-app)

[via npm](tutorials/first-app)

[Start coding](tutorials/first-app)

### [Learn signals](tutorials/signals)

[via the Playground](tutorials/signals)

[Start coding](tutorials/signals)

### [Deferrable views](tutorials/deferrable-views)

[via the Playground](tutorials/deferrable-views)

[Start coding](tutorials/deferrable-views)

### [Learn signals forms](tutorials/signal-forms)

[via the Playground](tutorials/signal-forms)

[Start coding](tutorials/signal-forms)

----
url: https://angular.dev/api/forms/signals/disabled
----

```
function disabled<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  config?:
    | {
        when?:
          | string
          | NoInfer<LogicFn<TValue, string | boolean, TPathKind>>
          | undefined;
      }
    | undefined,
): void;
function disabled<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  logic?:
    string | NoInfer<LogicFn<TValue, string | boolean, TPathKind>> | undefined,
): void;
```

Adds logic to a field to conditionally disable it. A disabled field does not contribute to the validation, touched/dirty, or other state of its parent field.

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to add the disabled logic to.

@paramconfig`{ when?: string | NoInfer<LogicFn<TValue, string | boolean, TPathKind>> | undefined; } | undefined`

Optional configuration object.

* `when`: A reactive function that returns `true` (or a string reason) when the field is disabled, and `false` when it is not disabled. Can also be a static string reason.

@returns`void`

Adds logic to a field to conditionally disable it.

@deprecated

Passing a function or string directly to `disabled` is deprecated. Use `{ when: ... }` instead.

@parampath`SchemaPath<TValue, 1, TPathKind>`

@paramlogic`string | NoInfer<LogicFn<TValue, string | boolean, TPathKind>> | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/provideZoneChangeDetection
----

```
function provideZoneChangeDetection(
  options?: NgZoneOptions | undefined,
): EnvironmentProviders;
```

@paramoptions`NgZoneOptions | undefined`

@returns`EnvironmentProviders`

## [Description](#description)

Provides [`NgZone`](/api/core/NgZone)-based change detection for the application bootstrapped using `bootstrapApplication`.

Add this provider to use [`NgZone`](/api/core/NgZone)/ZoneJS-based change detection and configure options like `eventCoalescing` in the [`NgZone`](/api/core/NgZone).

If you need this provider function in an NgModule-based application, pass it as `applicationProviders` to `bootstrapModule()`.

## [Usage Notes](#usage-notes)

```
bootstrapApplication(MyApp, {providers: [
  provideZoneChangeDetection({eventCoalescing: true}),
]});
```

Jump to details

----
url: https://angular.dev/api/router/withNavigationErrorHandler
----

```
function withNavigationErrorHandler(
  handler: (error: NavigationError) => unknown,
): NavigationErrorHandlerFeature;
```

@paramhandler`(error: NavigationError) => unknown`

@returns`NavigationErrorHandlerFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Description](#description)

Provides a function which is called when a navigation error occurs.

This function is run inside application's [injection context](guide/di/dependency-injection-context) so you can use the [`inject`](api/core/inject) function.

This function can return a [`RedirectCommand`](/api/router/RedirectCommand) to convert the error to a redirect, similar to returning a [`UrlTree`](/api/router/UrlTree) or [`RedirectCommand`](/api/router/RedirectCommand) from a guard. This will also prevent the [`Router`](/api/router/Router) from emitting [`NavigationError`](/api/router/NavigationError); it will instead emit [`NavigationCancel`](/api/router/NavigationCancel) with code NavigationCancellationCode.Redirect. Return values other than [`RedirectCommand`](/api/router/RedirectCommand) are ignored and do not change any behavior with respect to how the [`Router`](/api/router/Router) handles the error.

## [Usage Notes](#usage-notes)

Basic example of how you can use the error handler option:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withNavigationErrorHandler((e: NavigationError) =>
inject(MyErrorTracker).trackError(e)))
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8102
----

This diagnostic detects a useless nullish coalescing operator (`??`) characters in Angular templates. Specifically, it looks for operations where the input is not "nullable", meaning its type does not include `null` or `undefined`. For such values, the right side of the `??` will never be used.

```
import {Component} from '@angular/core';

@Component({
  // Template displays `username` if present, falls back to 'root' if it is
  // `null` or `undefined`.
  template: `<div>{{ username ?? 'root' }}</div>`,
})
class MyComponent {
  // `username` is declared as a `string` which _cannot_ be `null` or
  // `undefined`.
  username: string = 'Angelino';
}
```

## [What's wrong with that?](#whats-wrong-with-that)

Using the nullish coalescing operator with a non-nullable input has no effect and is indicative of a discrepancy between the allowed type of a value and how it is presented in the template. A developer might reasonably assume that the right side of the nullish coalescing operator is displayed in some case, but it will never actually be displayed. This can lead to confusion about the expected output of the program.

## [What should I do instead?](#what-should-i-do-instead)

Update the template and declared type to be in sync. Double-check the type of the input and confirm whether it is actually expected to be nullable.

If the input should be nullable, add `null` or `undefined` to its type to indicate this.

```
import {Component} from '@angular/core';

@Component({
  template: `<div>{{ username ?? 'root' }}</div>`,
})
class MyComponent {
  // `username` is now nullable. If it is ever set to `null`, 'root' will be
  // displayed.
  username: string | null = 'Angelino';
}
```

If the input should *not* be nullable, delete the `??` operator and its right operand, since the value is guaranteed by TypeScript to always be non-nullable.

```
import {Component} from '@angular/core';

@Component({
  // Template always displays `username`, which is guaranteed to never be `null`
  // or `undefined`.
  template: `<div>{{ username }}</div>`,
})
class MyComponent {
  username: string = 'Angelino';
}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. [`strictNullChecks`](tools/cli/template-typecheck#strict-null-checks) must also be enabled to emit `nullishCoalescingNotNullable` diagnostics.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "nullishCoalescingNotNullable": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/guide/testing/zone-js-testing-utilities
----

This guide describes testing utilities primarily used for managing and controlling async tasks in unit tests. These utilities are essentially the Zone.js-specific mock clock utilities, particularly relevant for controlling the flow of asynchronous operations within tests.

For general Angular testing utilities, including [`TestBed`](/api/core/testing/TestBed) and [`ComponentFixture`](/api/core/testing/ComponentFixture), see the [Testing Utility APIs](guide/testing/utility-apis) guide.

Here's a summary of Zone.js-specific functions:

| Function                                                         | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| :--------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`waitForAsync`](/api/core/testing/waitForAsync)                 | Tracks async tasks and completes the tests only once there are no longer any micro or macrotasks remaining in the test zone.                                                                                                                                                                                                                                                                                                                                                                                                               |
| [`fakeAsync`](/api/core/testing/fakeAsync)                       | Runs the body of a test (`it`) within a special *fakeAsync test zone*, enabling a linear control flow coding style.                                                                                                                                                                                                                                                                                                                                                                                                                        |
| [`tick`](/api/core/testing/tick)                                 | Simulates the passage of time and the completion of pending asynchronous activities by flushing both *timer* and *micro-task* queues within the *fakeAsync test zone*. The curious, dedicated reader might enjoy this lengthy blog post, ["*Tasks, microtasks, queues and schedules*"](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules). Accepts an optional argument that moves the virtual clock forward by the specified number of milliseconds, clearing asynchronous activities scheduled within that timeframe. |
| [`discardPeriodicTasks`](/api/core/testing/discardPeriodicTasks) | Discards any periodic tasks (e.g. `setInterval`) that were created inside the [`fakeAsync`](/api/core/testing/fakeAsync) Zone.                                                                                                                                                                                                                                                                                                                                                                                                             |
| [`flushMicrotasks`](/api/core/testing/flushMicrotasks)           | When a [`fakeAsync()`](/api/core/testing/fakeAsync) test ends with pending *micro-tasks* such as unresolved promises, the test fails with a clear error message. In general, a test should wait for micro-tasks to finish. When pending microtasks are expected, call [`flushMicrotasks`](/api/core/testing/flushMicrotasks) to flush the *micro-task* queue and avoid the error.                                                                                                                                                          |

----
url: https://angular.dev/api/core/ComponentRef
----

```
    
      abstract class ComponentRef<C> {}
    
    
```

Updates a specified input name to a new value. Using this method will properly mark for check component using the `OnPush` change detection strategy. It will also assure that the [`OnChanges`](/api/core/OnChanges) lifecycle hook runs when a dynamically created component is change-detected.

@paramname`string`

The name of an input.

@paramvalue`unknown`

The new value of an input.

@returns`void`

The host or anchor element for this component instance.

The dependency injector for this component instance.

This component instance.

The host view defined by the template for this component instance.

The change detector for this component instance.

The type of this component (as created by a `ComponentFactory` class).

Destroys the component instance and all of the data structures associated with it.

@returns`void`

A lifecycle hook that provides additional developer-defined cleanup functionality for the component.

@paramcallback`Function`

A handler function that cleans up developer-defined data associated with this component. Called when the `destroy()` method is invoked.

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/RouterConfigurationFeature
----

```
    
      type RouterConfigurationFeature = RouterFeature<RouterFeatureKind.RouterConfigurationFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/core/@boundary
----

**IMPORTANT:** The `@boundary` block is in [developer preview](reference/releases#developer-preview). It's ready for you to try, but it may change before it is stable.

The `@boundary` block is a control flow mechanism that lets you intercept and handle rendering errors in templates.

## [Syntax](#syntax)

```
@boundary {
  <app-risky-component />
} @error {
  <app-error-fallback [error]="err" />
}
```

## [Description](#description)

The `@boundary` block encapsulates its content and catches any errors that occur during the initial render or subsequent change detection cycles of its child views. If an error is caught, the framework stops rendering the main view and instead renders the fallback UI provided in the `@error` block.

**IMPORTANT:** Wrapping `<ng-content>` in a `@boundary` does not catch errors from projected content. See [Content projection with error boundaries](guide/templates/error-boundaries#content-projection).

You can capture the error object by declaring a variable in the `@error` block, for example `@error (let err)`.

You can use multiple `@error` blocks with `when` clauses to conditionally render different fallbacks based on the error type, with a final fallback `@error` block at the end. You can also access a `$reset` function to attempt re-rendering the boundary content.

```
@boundary {
  <app-risky-component />
} @error (let err; retry = $reset; when isNetworkError(err)) {
  <app-network-error [error]="err" />
  <button (click)="retry()">Retry</button>
} @error {
  <app-generic-error />
}
```

Jump to details

----
url: https://angular.dev/api/core/DebouncedOptions
----

## [API](/api/core/DebouncedOptions#api)

```
    
      interface DebouncedOptions<T> {}
    
    
```

The [`Injector`](/api/core/Injector) to use for the debounced resource.

The equality function to use for comparing values.

Jump to details

----
url: https://angular.dev/api/core/ResourceOptions
----

```
    
      type ResourceOptions<T, R> = (  | PromiseResourceOptions<T, R>  | StreamingResourceOptions<T, R>) & {  /**   * A debug name for the reactive node. Used in Angular DevTools to identify the node.   */  debugName?: string;}
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/SafeHtml
----

```
    
      interface SafeHtml extends SafeValue {}
    
    
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8101
----

This diagnostic detects a backwards "banana-in-box" syntax for [two-way bindings](guide/templates/two-way-binding).

```
<user-viewer ([user])="loggedInUser" />
```

## [What's wrong with that?](#whats-wrong-with-that)

As it stands, `([var])` is actually an [event binding](guide/templates/event-listeners) with the name `[var]`. The author likely intended this as a two-way binding to a variable named `var` but, as written, this binding has no effect.

## [What should I do instead?](#what-should-i-do-instead)

Fix the typo. As the name suggests, the banana `(` should go *inside* the box `[]`. In this case:

```
<user-viewer [(user)]="loggedInUser" />
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `invalidBananaInBox` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "invalidBananaInBox": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/core/NgZone
----

```
    
      class NgZone {}
    
    
```

@paramoptions`{ enableLongStackTrace?: boolean | undefined; shouldCoalesceEventChangeDetection?: boolean | undefined; shouldCoalesceRunChangeDetection?: boolean | undefined; }`

@returns`NgZone`

Whether there are no outstanding microtasks or macrotasks.

Notifies when code enters Angular Zone. This gets fired first on VM Turn.

Notifies when there is no more microtasks enqueued in the current VM Turn. This is a hint for Angular to do change detection, which may enqueue more microtasks. For this reason this event can fire multiple times per VM Turn.

Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which implies we are about to relinquish VM turn. This event gets called just once.

Notifies that an error has been delivered.

Executes the `fn` function synchronously within the Angular zone and returns value returned by the function.

Running functions via `run` allows you to reenter Angular zone from a task that was executed outside of the Angular zone (typically started via [`runOutsideAngular`](/api/core/NgZone#runOutsideAngular)).

Any future tasks or microtasks scheduled from within this function will continue executing from within the Angular zone.

If a synchronous error happens it will be rethrown and not reported via `onError`.

@paramfn`(...args: any[]) => T`

@paramapplyThis`any`

@paramapplyArgs`any[] | undefined`

@returns`T`

Executes the `fn` function synchronously within the Angular zone as a task and returns value returned by the function.

Running functions via `runTask` allows you to reenter Angular zone from a task that was executed outside of the Angular zone (typically started via [`runOutsideAngular`](/api/core/NgZone#runOutsideAngular)).

Any future tasks or microtasks scheduled from within this function will continue executing from within the Angular zone.

If a synchronous error happens it will be rethrown and not reported via `onError`.

@paramfn`(...args: any[]) => T`

@paramapplyThis`any`

@paramapplyArgs`any[] | undefined`

@paramname`string | undefined`

@returns`T`

Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not rethrown.

@paramfn`(...args: any[]) => T`

@paramapplyThis`any`

@paramapplyArgs`any[] | undefined`

@returns`T`

Executes the `fn` function synchronously in Angular's parent zone and returns value returned by the function.

Running functions via [`runOutsideAngular`](/api/core/NgZone#runOutsideAngular) allows you to escape Angular's zone and do work that doesn't trigger Angular change-detection or is subject to Angular's error handling.

Any future tasks or microtasks scheduled from within this function will continue executing from outside of the Angular zone.

Use [`run`](/api/core/NgZone#run) to reenter the Angular zone and do work that updates the application model.

@paramfn`(...args: any[]) => T`

@returns`T`

This method checks whether the method call happens within an Angular Zone instance.

@returns`boolean`

Assures that the method is called within the Angular Zone, otherwise throws an error.

@returns`void`

Assures that the method is called outside of the Angular Zone, otherwise throws an error.

@returns`void`

## [Description](#description)

An injectable service for executing work inside or outside of the Angular zone.

The most common use of this service is to optimize performance when starting a work consisting of one or more asynchronous tasks that don't require UI updates or error handling to be handled by Angular. Such tasks can be kicked off via [`runOutsideAngular`](/api/core/NgZone#runOutsideAngular) and if needed, these tasks can reenter the Angular zone via [`run`](/api/core/NgZone#run).

## [Usage Notes](#usage-notes)

### [Example](#example)

```
import {Component, NgZone} from '@angular/core';

@Component({
  selector: 'ng-zone-demo',
  template: `
    <h2>Demo: NgZone</h2>

    <p>Progress: {{progress}}%</p>
    @if(progress >= 100) {
       <p>Done processing {{label}} of Angular zone!</p>
    }

    <button (click)="processWithinAngularZone()">Process within Angular zone</button>
    <button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button>
  `,
})
export class NgZoneDemo {
  progress: number = 0;
  label: string;

  constructor(private _ngZone: NgZone) {}

  // Loop inside the Angular zone
  // so the UI DOES refresh after each setTimeout cycle
  processWithinAngularZone() {
    this.label = 'inside';
    this.progress = 0;
    this._increaseProgress(() => console.log('Inside Done!'));
  }

  // Loop outside of the Angular zone
  // so the UI DOES NOT refresh after each setTimeout cycle
  processOutsideOfAngularZone() {
    this.label = 'outside';
    this.progress = 0;
    this._ngZone.runOutsideAngular(() => {
      this._increaseProgress(() => {
        // reenter the Angular zone and display done
        this._ngZone.run(() => { console.log('Outside Done!'); });
      });
    });
  }

  _increaseProgress(doneCallback: () => void) {
    this.progress += 1;
    console.log(`Current progress: ${this.progress}%`);

    if (this.progress < 100) {
      window.setTimeout(() => this._increaseProgress(doneCallback), 10);
    } else {
      doneCallback();
    }
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/InjectableProvider
----

## [API](/api/core/InjectableProvider#api)

```
    
      type InjectableProvider = | ValueSansProvider  | ExistingSansProvider  | StaticClassSansProvider  | ConstructorSansProvider  | FactorySansProvider  | ClassSansProvider
    
    
```

Jump to details

----
url: https://angular.dev/api/common/NgComponentOutlet
----

```
    
      class NgComponentOutlet<T = any> implements OnChanges ,DoCheck ,OnDestroy {}
    
    
```

@param\_viewContainerRef`ViewContainerRef`

@returns`NgComponentOutlet<T>`

Component that should be rendered in the outlet.

Gets the instance of the currently-rendered component. Will be null if no component has been rendered.

## [Description](#description)

Instantiates a [`Component`](/api/core/Component) type and inserts its Host View into the current View. [`NgComponentOutlet`](/api/common/NgComponentOutlet) provides a declarative approach for dynamic component creation.

[`NgComponentOutlet`](/api/common/NgComponentOutlet) requires a component type, if a falsy value is set the view will clear and any existing component will be destroyed.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

### [Fine tune control](#fine-tune-control)

You can control the component creation process by using the following optional attributes:

* `ngComponentOutletInputs`: Optional component inputs object, which will be bind to the component.

* `ngComponentOutletInjector`: Optional custom [`Injector`](/api/core/Injector) that will be used as parent for the Component. Defaults to the injector of the current view container.

* `ngComponentOutletEnvironmentInjector`: Optional custom [`EnvironmentInjector`](/api/core/EnvironmentInjector) which will provide the component's environment.

* `ngComponentOutletContent`: Optional list of projectable nodes to insert into the content section of the component, if it exists.

* `ngComponentOutletNgModule`: Optional NgModule class reference to allow loading another module dynamically, then loading a component from that module.

### [Syntax](#syntax)

Simple

```
<ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
```

With inputs

```
<ng-container *ngComponentOutlet="componentTypeExpression;
                                  inputs: inputsExpression;">
</ng-container>
```

Customized injector/content

```
<ng-container *ngComponentOutlet="componentTypeExpression;
                                  injector: injectorExpression;
                                  content: contentNodesExpression;">
</ng-container>
```

Customized NgModule reference

```
<ng-container *ngComponentOutlet="componentTypeExpression;
                                  ngModule: ngModuleClass;">
</ng-container>
```

### [A simple example](#a-simple-example)

```
@Component({
  selector: 'hello-world',
  template: 'Hello World!',
})
export class HelloWorld {}

@Component({
  selector: 'ng-component-outlet-simple-example',
  imports: [NgComponentOutlet],
  template: `<ng-container *ngComponentOutlet="HelloWorld"></ng-container>`,
})
export class NgComponentOutletSimpleExample {
  // This field is necessary to expose HelloWorld to the template.
  HelloWorld = HelloWorld;
}
```

A more complete example with additional options:

```
@Injectable()
export class Greeter {
  suffix = '!';
}

@Component({
  selector: 'complete-component',
  template: `{{ label() }}: <ng-content></ng-content> <ng-content></ng-content
    >{{ greeter.suffix }}`,
})
export class CompleteComponent {
  label = input.required<string>();

  constructor(public greeter: Greeter) {}
}

@Component({
  selector: 'ng-component-outlet-complete-example',
  imports: [NgComponentOutlet],
  template: ` <ng-template #ahoj>Ahoj</ng-template>
    <ng-template #svet>Svet</ng-template>
    <ng-container
      *ngComponentOutlet="
        CompleteComponent;
        inputs: myInputs;
        injector: myInjector;
        content: myContent
      "
    ></ng-container>`,
})
export class NgComponentOutletCompleteExample {
  // This field is necessary to expose CompleteComponent to the template.
  CompleteComponent = CompleteComponent;

  myInputs = {'label': 'Complete'};

  myInjector: Injector;
  ahojTemplateRef = viewChild.required<TemplateRef<any>>('ahoj');
  svetTemplateRef = viewChild.required<TemplateRef<any>>('svet');
  myContent?: any[][];

  constructor(
    injector: Injector,
    private vcr: ViewContainerRef,
  ) {
    this.myInjector = Injector.create({
      providers: [{provide: Greeter, deps: []}],
      parent: injector,
    });

    effect(() => {
      this.myContent = [
        this.vcr.createEmbeddedView(this.ahojTemplateRef()).rootNodes,
        this.vcr.createEmbeddedView(this.svetTemplateRef()).rootNodes,
      ];
    });
  }
}
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpEventType
----

```
    
      enum HttpEventType {}
    
    
```

The request was sent out over the wire.

An upload progress event was received.

Note: The [`FetchBackend`](/api/common/http/FetchBackend) doesn't support progress report on uploads.

The response status code and headers were received.

A download progress event was received.

The full response including the body was received.

A custom event from an interceptor or a backend.

Jump to details

----
url: https://angular.dev/api/core/testing/TestComponentRenderer
----

```
    
      class TestComponentRenderer {}
    
    
```

@paramrootElementId`string`

@paramtagName`string | undefined`

@returns`void`

@returns`void`

Jump to details

----
url: https://angular.dev/api/forms/FormControlName
----

```
    
      class FormControlName extends NgControl implements OnChanges ,OnDestroy {}
    
    
```

@paramparent`ControlContainer`

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@paramvalueAccessors`ControlValueAccessor[]`

@param\_ngModelWarningConfig`string | null`

@paramrenderer`Renderer2 | undefined`

@paraminjector`Injector | undefined`

@returns`FormControlName`

Tracks the [`FormControl`](/api/forms/FormControl) instance bound to the directive.

Tracks the name of the [`FormControl`](/api/forms/FormControl) bound to the directive. The name corresponds to a key in the parent [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray). Accepts a name as a string or a number. The name in the form of a string is useful for individual forms, while the numerical form allows for form controls to be bound to indices when iterating over controls in a [`FormArray`](/api/forms/FormArray).

Triggers a warning in dev mode that this input should not be used with reactive forms.

@deprecated

as of v6

@deprecated

as of v6

Sets the new value for the view model and emits an `ngModelChange` event.

@paramnewValue`any`

The new value for the view model.

@returns`void`

Returns an array that represents the path from the top-level form to this control. Each index is the string name of the control on that level.

The top-level directive for this group if present, otherwise null.

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Syncs a [`FormControl`](/api/forms/FormControl) in an existing [`FormGroup`](/api/forms/FormGroup) to a form control element by name.

## [Exported by](#exported-by)

* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

### [Register ``](#register-formcontrol-within-a-group)`FormControl` within a group

The following example shows how to register multiple form controls within a form group and set their value.

```
import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      @if (first.invalid) {
        <div>Name is too short.</div>
      }
      <input formControlName="first" placeholder="First name" />
      <input formControlName="last" placeholder="Last name" />

      <button type="submit">Submit</button>
    </form>
    <button (click)="setValue()">Set preset value</button>
  `,
  standalone: false,
})
export class SimpleFormGroup {
  form = new FormGroup({
    first: new FormControl('Nancy', Validators.minLength(2)),
    last: new FormControl('Drew'),
  });

  get first(): any {
    return this.form.get('first');
  }

  onSubmit(): void {
    console.log(this.form.value); // {first: 'Nancy', last: 'Drew'}
  }

  setValue() {
    this.form.setValue({first: 'Carson', last: 'Drew'});
  }
}
```

To see `formControlName` examples with different form control types, see:

* Radio buttons: [`RadioControlValueAccessor`](/api/forms/RadioControlValueAccessor)
* Selects: [`SelectControlValueAccessor`](/api/forms/SelectControlValueAccessor)

### [Use with ngModel is deprecated](#use-with-ngmodel-is-deprecated)

Support for using the `ngModel` input property and `ngModelChange` event with reactive form directives has been deprecated in Angular v6 and is scheduled for removal in a future version of Angular.

Jump to details

----
url: https://angular.dev/api/router/ActivationEnd
----

```
    
      class ActivationEnd {}
    
    
```

@paramsnapshot`ActivatedRouteSnapshot`

@returns`ActivationEnd`

Jump to details

----
url: https://angular.dev/cli/cache/disable
----

```
ng cache disable
```

```
ng cache off
```

Disables persistent disk cache for all projects in the workspace.

Jump to details

----
url: https://angular.dev/api/core/ErrorDetails
----

```
    
      interface ErrorDetails {}
    
    
```

The class of the component or directive where the error occurred.

The instance of the component or directive where the error occurred.

Information about the error boundary that caught the error.

The function that intercepted the error, if any.

Jump to details

----
url: https://angular.dev/api/core/ResourceRef
----

```
    
      interface ResourceRef<T> extends WritableResource<T> {
}
    
    
```

@paramthis`T extends undefined ? this : never`

@returns`this is ResourceRef<Exclude<T, undefined>>`

@returns`boolean`

Manually destroy the resource, which cancels pending requests and returns it to `idle` state.

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/setTestabilityGetter
----

```
function setTestabilityGetter(getter: GetTestability): void;
```

@paramgetter`GetTestability`

@returns`void`

Jump to details

----
url: https://angular.dev/guide/i18n
----

In-depth Guides

Internationalization

# Angular Internationalization (i18n)

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/i18n/overview.md "Edit this page")

*Internationalization*, sometimes referenced as i18n, is the process of designing and preparing your project for use in different locales around the world. *Localization* is the process of building versions of your project for different locales. The localization process includes the following actions.

* Extract text for translation into different languages
* Format data for a specific locale

A *locale* identifies a region in which people speak a particular language or language variant. Possible regions include countries and geographical regions. A locale determines the formatting and parsing of the following details.

* Measurement units including date and time, numbers, and currencies
* Translated names including time zones, languages, and countries

For a quick introduction to localization and internationalization watch this video:

[](https://www.youtube.com/watch?v=KNTN-nsbV7M\&autoplay=1)

## [Learn about Angular internationalization](#learn-about-angular-internationalization)

### [Add the localize package](guide/i18n/add-package)

[Learn how to add the Angular Localize package to your project](guide/i18n/add-package)

[Learn more](guide/i18n/add-package)

### [Refer to locales by ID](guide/i18n/locale-id)

[Learn how to identify and specify a locale identifier for your project](guide/i18n/locale-id)

[Learn more](guide/i18n/locale-id)

### [Format data based on locale](guide/i18n/format-data-locale)

[Learn how to implement localized data pipes and override the locale for your project](guide/i18n/format-data-locale)

[Learn more](guide/i18n/format-data-locale)

### [Prepare component for translation](guide/i18n/prepare)

[Learn how to specify source text for translation](guide/i18n/prepare)

[Learn more](guide/i18n/prepare)

### [Work with translation files](guide/i18n/translation-files)

[Learn how to review and process translation text](guide/i18n/translation-files)

[Learn more](guide/i18n/translation-files)

### [Merge translations into the application](guide/i18n/merge)

[Learn how to merge translations and build your translated application](guide/i18n/merge)

[Learn more](guide/i18n/merge)

### [Deploy multiple locales](guide/i18n/deploy)

[Learn how to deploy multiple locales for your application](guide/i18n/deploy)

[Learn more](guide/i18n/deploy)

### [Import global variants of the locale data](guide/i18n/import-global-variants)

[Learn how to import locale data for language variants](guide/i18n/import-global-variants)

[Learn more](guide/i18n/import-global-variants)

### [Manage marked text with custom IDs](guide/i18n/manage-marked-text)

[Learn how to implement custom IDs to help you manage your marked text](guide/i18n/manage-marked-text)

[Learn more](guide/i18n/manage-marked-text)

### [Internationalization example](guide/i18n/example)

[Review an example of Angular internationalization.](guide/i18n/example)

[Learn more](guide/i18n/example)

----
url: https://angular.dev/guide/forms/dynamic-forms
----

Many forms, such as questionnaires, can be very similar to one another in format and intent. To make it faster and easier to generate different versions of such a form, you can create a *dynamic form template* based on metadata that describes the business object model. Then, use the template to generate new forms automatically, according to changes in the data model.

The technique is particularly useful when you have a type of form whose content must change frequently to meet rapidly changing business and regulatory requirements. A typical use-case is a questionnaire. You might need to get input from users in different contexts. The format and style of the forms a user sees should remain constant, while the actual questions you need to ask vary with the context.

In this tutorial, you will build a dynamic form that presents a basic questionnaire. You build an online application for heroes seeking employment. The agency is constantly tinkering with the application process, but by using the dynamic form you can create the new forms on the fly without changing the application code.

The tutorial walks you through the following steps:

1. Enable reactive forms for a project.
2. Establish a data model to represent form controls.
3. Populate the model with sample data.
4. Develop a component to create form controls dynamically.

The form you create uses input validation and styling to improve the user experience. It has a Submit button that is only enabled when all user input is valid, and flags invalid input with color coding and error messages.

The basic version can evolve to support a richer variety of questions, more graceful rendering, and superior user experience.

## [Enable reactive forms for your project](#enable-reactive-forms-for-your-project)

Dynamic forms are based on reactive forms.

To give the application access to reactive form directives, import [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule) from the `@angular/forms` package into the necessary components.

### dynamic-form.component.ts

```
import {Component, computed, inject, input} from '@angular/core';
import {FormGroup, ReactiveFormsModule} from '@angular/forms';

import {DynamicFormQuestionComponent} from './dynamic-form-question.component';

import {QuestionBase} from './question-base';
import {QuestionControlService} from './question-control.service';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  providers: [QuestionControlService],
  imports: [DynamicFormQuestionComponent, ReactiveFormsModule],
})
export class DynamicFormComponent {
  private readonly qcs = inject(QuestionControlService);

  readonly questions = input<QuestionBase<string>[] | null>([]);
  readonly form = computed<FormGroup>(() =>
    this.qcs.toFormGroup(this.questions() as QuestionBase<string>[]),
  );
  payLoad = '';

  onSubmit() {
    this.payLoad = JSON.stringify(this.form().getRawValue());
  }
}
```

### dynamic-form-question.component.ts

```
import {Component, input, Input} from '@angular/core';
import {FormGroup, ReactiveFormsModule} from '@angular/forms';

import {QuestionBase} from './question-base';

@Component({
  selector: 'app-question',
  templateUrl: './dynamic-form-question.component.html',
  imports: [ReactiveFormsModule],
})
export class DynamicFormQuestionComponent {
  readonly question = input.required<QuestionBase<string>>();
  readonly form = input.required<FormGroup>();

  get isValid() {
    return this.form().controls[this.question().key].valid;
  }
}
```

## [Create a form object model](#create-a-form-object-model)

A dynamic form requires an object model that can describe all scenarios needed by the form functionality. The example hero-application form is a set of questions — that is, each control in the form must ask a question and accept an answer.

The data model for this type of form must represent a question. The example includes the `DynamicFormQuestionComponent`, which defines a question as the fundamental object in the model.

The following `QuestionBase` is a base class for a set of controls that can represent the question and its answer in the form.

### question-base.ts

```
export class QuestionBase<T> {
  value: T | undefined;
  key: string;
  label: string;
  required: boolean;
  order: number;
  controlType: string;
  type: string;
  options: {key: string; value: string}[];

  constructor(
    options: {
      value?: T;
      key?: string;
      label?: string;
      required?: boolean;
      order?: number;
      controlType?: string;
      type?: string;
      options?: {key: string; value: string}[];
    } = {},
  ) {
    this.value = options.value;
    this.key = options.key || '';
    this.label = options.label || '';
    this.required = !!options.required;
    this.order = options.order === undefined ? 1 : options.order;
    this.controlType = options.controlType || '';
    this.type = options.type || '';
    this.options = options.options || [];
  }
}
```

### [Define control classes](#define-control-classes)

From this base, the example derives two new classes, `TextboxQuestion` and `DropdownQuestion`, that represent different control types. When you create the form template in the next step, you instantiate these specific question types in order to render the appropriate controls dynamically.

The `TextboxQuestion` control type is represented in a form template using an `<input>` element. It presents a question and lets users enter input. The `type` attribute of the element is defined based on the `type` field specified in the `options` argument (for example `text`, `email`, `url`).

### question-textbox.ts

```
import {QuestionBase} from './question-base';

export class TextboxQuestion extends QuestionBase<string> {
  override controlType = 'textbox';
}
```

The `DropdownQuestion` control type presents a list of choices in a select box.

### question-dropdown.ts

```
import {QuestionBase} from './question-base';

export class DropdownQuestion extends QuestionBase<string> {
  override controlType = 'dropdown';
}
```

### [Compose form groups](#compose-form-groups)

A dynamic form uses a service to create grouped sets of input controls, based on the form model. The following `QuestionControlService` collects a set of [`FormGroup`](/api/forms/FormGroup) instances that consume the metadata from the question model. You can specify default values and validation rules.

### question-control.service.ts

```
import {Injectable} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

import {QuestionBase} from './question-base';

@Injectable()
export class QuestionControlService {
  toFormGroup(questions: QuestionBase<string>[]) {
    const group: any = {};

    questions.forEach((question) => {
      group[question.key] = question.required
        ? new FormControl(question.value || '', Validators.required)
        : new FormControl(question.value || '');
    });
    return new FormGroup(group);
  }
}
```

## [Compose dynamic form contents](#compose-dynamic-form-contents)

The dynamic form itself is represented by a container component, which you add in a later step. Each question is represented in the form component's template by an `<app-question>` tag, which matches an instance of `DynamicFormQuestionComponent`.

The `DynamicFormQuestionComponent` is responsible for rendering the details of an individual question based on values in the data-bound question object. The form relies on a [`[formGroup]` directive](api/forms/FormGroupDirective "API reference") to connect the template HTML to the underlying control objects. The `DynamicFormQuestionComponent` creates form groups and populates them with controls defined in the question model, specifying display and validation rules.

### dynamic-form-question.component.html

```
<div [formGroup]="form()">
  <label [attr.for]="question().key">{{ question().label }}</label>

  <div>
    @switch (question().controlType) {
      @case ('textbox') {
        <input [formControlName]="question().key" [id]="question().key" [type]="question().type" />
      }
      @case ('dropdown') {
        <select [id]="question().key" [formControlName]="question().key">
          @for (opt of question().options; track opt) {
            <option [value]="opt.key">{{ opt.value }}</option>
          }
        </select>
      }
    }
  </div>

  @if (!isValid) {
    <div class="errorMessage">{{ question().label }} is required</div>
  }
</div>
```

### dynamic-form-question.component.ts

```
import {Component, input, Input} from '@angular/core';
import {FormGroup, ReactiveFormsModule} from '@angular/forms';

import {QuestionBase} from './question-base';

@Component({
  selector: 'app-question',
  templateUrl: './dynamic-form-question.component.html',
  imports: [ReactiveFormsModule],
})
export class DynamicFormQuestionComponent {
  readonly question = input.required<QuestionBase<string>>();
  readonly form = input.required<FormGroup>();

  get isValid() {
    return this.form().controls[this.question().key].valid;
  }
}
```

The goal of the `DynamicFormQuestionComponent` is to present question types defined in your model. You only have two types of questions at this point but you can imagine many more. The `@switch` block in the template determines which type of question to display. The switch uses directives with the [`formControlName`](api/forms/FormControlName "FormControlName directive API reference") and [`formGroup`](api/forms/FormGroupDirective "FormGroupDirective API reference") selectors. Both directives are defined in [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule).

### [Supply data](#supply-data)

Another service is needed to supply a specific set of questions from which to build an individual form. For this exercise you create the `QuestionService` to supply this array of questions from the hard-coded sample data. In a real-world app, the service might fetch data from a backend system. The key point, however, is that you control the hero job-application questions entirely through the objects returned from `QuestionService`. To maintain the questionnaire as requirements change, you only need to add, update, and remove objects from the `questions` array.

The `QuestionService` supplies a set of questions in the form of an array bound to [`input()`](/api/core/input) questions.

### question.service.ts

```
import {Injectable} from '@angular/core';

import {DropdownQuestion} from './question-dropdown';
import {QuestionBase} from './question-base';
import {TextboxQuestion} from './question-textbox';
import {of} from 'rxjs';

@Injectable()
export class QuestionService {
  // TODO: get from a remote source of question metadata
  getQuestions() {
    const questions: QuestionBase<string>[] = [
      new DropdownQuestion({
        key: 'favoriteAnimal',
        label: 'Favorite Animal',
        options: [
          {key: 'cat', value: 'Cat'},
          {key: 'dog', value: 'Dog'},
          {key: 'horse', value: 'Horse'},
          {key: 'capybara', value: 'Capybara'},
        ],
        order: 3,
      }),

      new TextboxQuestion({
        key: 'firstName',
        label: 'First name',
        value: 'Alex',
        required: true,
        order: 1,
      }),

      new TextboxQuestion({
        key: 'emailAddress',
        label: 'Email',
        type: 'email',
        order: 2,
      }),
    ];

    return of(questions.sort((a, b) => a.order - b.order));
  }
}
```

## [Create a dynamic form template](#create-a-dynamic-form-template)

The `DynamicFormComponent` component is the entry point and the main container for the form, which is represented using the `<app-dynamic-form>` in a template.

The `DynamicFormComponent` component presents a list of questions by binding each one to an `<app-question>` element that matches the `DynamicFormQuestionComponent`.

### dynamic-form.component.html

```
<div>
  <form (ngSubmit)="onSubmit()" [formGroup]="form()">
    @for (question of questions(); track question) {
      <div class="form-row">
        <app-question [question]="question" [form]="form()" />
      </div>
    }

    <div class="form-row">
      <button type="submit" [disabled]="!form().valid">Save</button>
    </div>
  </form>

  @if (payLoad) {
    <div class="form-row"><strong>Saved the following values</strong><br />{{ payLoad }}</div>
  }
</div>
```

### dynamic-form.component.ts

```
import {Component, computed, inject, input} from '@angular/core';
import {FormGroup, ReactiveFormsModule} from '@angular/forms';

import {DynamicFormQuestionComponent} from './dynamic-form-question.component';

import {QuestionBase} from './question-base';
import {QuestionControlService} from './question-control.service';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  providers: [QuestionControlService],
  imports: [DynamicFormQuestionComponent, ReactiveFormsModule],
})
export class DynamicFormComponent {
  private readonly qcs = inject(QuestionControlService);

  readonly questions = input<QuestionBase<string>[] | null>([]);
  readonly form = computed<FormGroup>(() =>
    this.qcs.toFormGroup(this.questions() as QuestionBase<string>[]),
  );
  payLoad = '';

  onSubmit() {
    this.payLoad = JSON.stringify(this.form().getRawValue());
  }
}
```

### [Display the form](#display-the-form)

To display an instance of the dynamic form, the `AppComponent` shell template passes the `questions` array returned by the `QuestionService` to the form container component, `<app-dynamic-form>`.

### app.component.ts

```
import {Component, inject} from '@angular/core';
import {AsyncPipe} from '@angular/common';

import {DynamicFormComponent} from './dynamic-form.component';

import {QuestionService} from './question.service';
import {QuestionBase} from './question-base';
import {Observable} from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    <div>
      <h2>Job Application for Heroes</h2>
      <app-dynamic-form [questions]="questions$ | async" />
    </div>
  `,
  providers: [QuestionService],
  imports: [AsyncPipe, DynamicFormComponent],
})
export class AppComponent {
  questions$: Observable<QuestionBase<string>[]> = inject(QuestionService).getQuestions();
}
```

This separation of model and data lets you repurpose the components for any type of survey, as long as it's compatible with the *question* object model.

### [Ensuring valid data](#ensuring-valid-data)

The form template uses dynamic data binding of metadata to render the form without making any hardcoded assumptions about specific questions. It adds both control metadata and validation criteria dynamically.

To ensure valid input, the *Save* button is disabled until the form is in a valid state. When the form is valid, click *Save* and the application renders the current form values as JSON.

The following figure shows the final form.

## [Next steps](#next-steps)

----
url: https://angular.dev/tutorials/first-app/08-ngFor
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/07-dynamic-template-values)[](/tutorials/first-app/09-services)

[](/tutorials/first-app/07-dynamic-template-values)[](/tutorials/first-app/09-services)

This tutorial lesson demonstrates how to use `@for` block in Angular templates in order to display dynamically repeated data in a template.

[](https://www.youtube.com/watch?v=eM3zi_n7lNs\&autoplay=1)

**NOTE:** This video reflects an older syntax, but the main concepts remain valid.

## [What you'll learn](#what-youll-learn)

* You will have added a data set to the app
* Your app will display a list of elements from the new data set using `@for`

## [Conceptual preview of `@for`](#conceptual-preview-of-for)

In Angular, `@for` is a specific type of [control flow block](/guide/templates/control-flow) used to dynamically repeat data in a template. In plain JavaScript you would use a for loop - `@for` provides similar functionality for Angular templates.

You can utilize `@for` to iterate over arrays and even asynchronous values. In this lesson, you'll add a new array of data to iterate over.

For a more in depth explanation, please refer to the [control flow](guide/templates/control-flow#repeat-content-with-the-for-block) guide.

1. ### [Add housing data to the `Home`](#add-housing-data-to-the-home)

   In the `Home` there is only a single housing location. In this step, you will add an array of `HousingLocation` entries.

   1. In `src/app/home/home.ts`, remove the `housingLocation` property from the `Home` class.

   2. Update the `Home` class to have a property called `housingLocationList`. Update your code to match the following code:

      ### Add housingLocationList property in home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of housingLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        housingLocationList: HousingLocationInfo[] = [
          {
            id: 0,
            name: 'Acme Fresh Start Housing',
            city: 'Chicago',
            state: 'IL',
            photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`,
            availableUnits: 4,
            wifi: true,
            laundry: true,
          },
          {
            id: 1,
            name: 'A113 Transitional Housing',
            city: 'Santa Monica',
            state: 'CA',
            photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`,
            availableUnits: 0,
            wifi: false,
            laundry: true,
          },
          {
            id: 2,
            name: 'Warm Beds Housing Support',
            city: 'Juneau',
            state: 'AK',
            photo: `${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg`,
            availableUnits: 1,
            wifi: false,
            laundry: false,
          },
          {
            id: 3,
            name: 'Homesteady Housing',
            city: 'Chicago',
            state: 'IL',
            photo: `${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg`,
            availableUnits: 1,
            wifi: true,
            laundry: false,
          },
          {
            id: 4,
            name: 'Happy Homes Group',
            city: 'Gary',
            state: 'IN',
            photo: `${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg`,
            availableUnits: 1,
            wifi: true,
            laundry: false,
          },
          {
            id: 5,
            name: 'Hopeful Apartment Group',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg`,
            availableUnits: 2,
            wifi: true,
            laundry: true,
          },
          {
            id: 6,
            name: 'Seriously Safe Towns',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg`,
            availableUnits: 5,
            wifi: true,
            laundry: true,
          },
          {
            id: 7,
            name: 'Hopeful Housing Solutions',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg`,
            availableUnits: 2,
            wifi: true,
            laundry: true,
          },
          {
            id: 8,
            name: 'Seriously Safe Towns',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg`,
            availableUnits: 10,
            wifi: false,
            laundry: false,
          },
          {
            id: 9,
            name: 'Capital Safe Towns',
            city: 'Portland',
            state: 'OR',
            photo: `${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg`,
            availableUnits: 6,
            wifi: true,
            laundry: true,
          },
        ];
      }
      ```

      **IMPORTANT:** Do not remove the [`@Component`](/api/core/Component) decorator, you will update that code in an upcoming step.

2. ### [Update the `Home` template to use `@for`](#update-the-home-template-to-use-for)

   Now the app has a dataset that you can use to display the entries in the browser using the `@for` block.

   1. Update the `<app-housing-location>` tag in the template code to this:

      ### Add @for to Home template in home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of housingLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        housingLocationList: HousingLocationInfo[] = [
          {
            id: 0,
            name: 'Acme Fresh Start Housing',
            city: 'Chicago',
            state: 'IL',
            photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`,
            availableUnits: 4,
            wifi: true,
            laundry: true,
          },
          {
            id: 1,
            name: 'A113 Transitional Housing',
            city: 'Santa Monica',
            state: 'CA',
            photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`,
            availableUnits: 0,
            wifi: false,
            laundry: true,
          },
          {
            id: 2,
            name: 'Warm Beds Housing Support',
            city: 'Juneau',
            state: 'AK',
            photo: `${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg`,
            availableUnits: 1,
            wifi: false,
            laundry: false,
          },
          {
            id: 3,
            name: 'Homesteady Housing',
            city: 'Chicago',
            state: 'IL',
            photo: `${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg`,
            availableUnits: 1,
            wifi: true,
            laundry: false,
          },
          {
            id: 4,
            name: 'Happy Homes Group',
            city: 'Gary',
            state: 'IN',
            photo: `${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg`,
            availableUnits: 1,
            wifi: true,
            laundry: false,
          },
          {
            id: 5,
            name: 'Hopeful Apartment Group',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg`,
            availableUnits: 2,
            wifi: true,
            laundry: true,
          },
          {
            id: 6,
            name: 'Seriously Safe Towns',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg`,
            availableUnits: 5,
            wifi: true,
            laundry: true,
          },
          {
            id: 7,
            name: 'Hopeful Housing Solutions',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg`,
            availableUnits: 2,
            wifi: true,
            laundry: true,
          },
          {
            id: 8,
            name: 'Seriously Safe Towns',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg`,
            availableUnits: 10,
            wifi: false,
            laundry: false,
          },
          {
            id: 9,
            name: 'Capital Safe Towns',
            city: 'Portland',
            state: 'OR',
            photo: `${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg`,
            availableUnits: 6,
            wifi: true,
            laundry: true,
          },
        ];
      }
      ```

      Note, in the code `[housingLocation] = "housingLocation"` the `housingLocation` value now refers to the variable used in the `@for` block. Before this change, it referred to the property on the `Home` class.

   2. Save all changes.

   3. Refresh the browser and confirm that the app now renders a grid of housing locations.

**SUMMARY:** In this lesson, you used the `@for` block to repeat data dynamically in Angular templates. You also added a new array of data to be used in the Angular app. The application now dynamically renders a list of housing locations in the browser.

The app is taking shape, great job.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/api/cdk/testing/HarnessPredicate
----

```
    
      class HarnessPredicate<T extends ComponentHarness> {}
    
    
```

@paramharnessType`ComponentHarnessConstructor<T>`

@paramoptions`BaseHarnessFilters`

@returns`HarnessPredicate<T>`

Adds a predicate function to be run against candidate harnesses.

@paramdescription`string`

A description of this predicate that may be used in error messages.

@parampredicate`AsyncPredicate<T>`

An async predicate function.

@returns`this`

Adds a predicate function that depends on an option value to be run against candidate harnesses. If the option value is undefined, the predicate will be ignored.

@paramname`string`

The name of the option (may be used in error messages).

@paramoption`O | undefined`

The option value.

@parampredicate`AsyncOptionPredicate<T, O>`

The predicate function to run if the option value is not undefined.

@returns`this`

Filters a list of harnesses on this predicate.

@paramharnesses`T[]`

The list of harnesses to filter.

@returns`Promise<T[]>`

Evaluates whether the given harness satisfies this predicate.

@paramharness`T`

The harness to check

@returns`Promise<boolean>`

Gets a description of this predicate for use in error messages.

@returns`string`

Gets the selector used to find candidate elements.

@returns`string`

Checks if the specified nullable string value matches the given pattern.

@paramvalue`string | Promise<string | null> | null`

The nullable string value to check, or a Promise resolving to the nullable string value.

@parampattern`string | RegExp | null`

The pattern the value is expected to match. If `pattern` is a string, `value` is expected to match exactly. If `pattern` is a regex, a partial match is allowed. If `pattern` is `null`, the value is expected to be `null`.

@returns`Promise<boolean>`

Jump to details

----
url: https://angular.dev/tools/cli/end-to-end
----

End-to-end or (E2E) testing is a form of testing used to assert your entire application works as expected from start to finish or *"end-to-end"*. E2E testing differs from unit testing in that it is completely decoupled from the underlying implementation details of your code. It is typically used to validate an application in a way that mimics the way a user would interact with it. This page serves as a guide to getting started with end-to-end testing in Angular using the Angular CLI.

## [Set Up E2E Testing](#set-up-e2e-testing)

The Angular CLI downloads and installs everything you need to run end-to-end tests for your Angular application.

```
ng e2e
```

The `ng e2e` command will first check your project for the "e2e" target. If it can't locate it, the CLI will then prompt you which e2e package you would like to use and walk you through the setup.

```
Cannot find "e2e" target for the specified project.
You can add a package that implements these capabilities.

For example:
Cypress: ng add @cypress/schematic
Nightwatch: ng add @nightwatch/schematics
WebdriverIO: ng add @wdio/schematics
Playwright: ng add playwright-ng-schematics
Puppeteer: ng add @puppeteer/ng-schematics

Would you like to add a package with "e2e" capabilities now?
No
❯ Cypress
Nightwatch
WebdriverIO
Playwright
Puppeteer
```

If you don't find the test runner you would like to use from the list above, you can manually add a package using `ng add`.

## [Running E2E Tests](#running-e2e-tests)

Now that your application is configured for end-to-end testing we can now run the same command to execute your tests.

```
ng e2e
```

Note, there isn't anything "special" about running your tests with any of the integrated e2e packages. The `ng e2e` command is really just running the `e2e` builder under the hood. You can always [create your own custom builder](tools/cli/cli-builder#creating-a-builder) named `e2e` and run it using `ng e2e`.

## [More information on end-to-end testing tools](#more-information-on-end-to-end-testing-tools)

| Testing Tool | Details                                                                                                              |
| :----------- | :------------------------------------------------------------------------------------------------------------------- |
| Cypress      | [Getting started with Cypress](https://docs.cypress.io/guides/end-to-end-testing/writing-your-first-end-to-end-test) |
| Nightwatch   | [Getting started with Nightwatch](https://nightwatchjs.org/guide/writing-tests/introduction.html)                    |
| WebdriverIO  | [Getting started with Webdriver.io](https://webdriver.io/docs/gettingstarted)                                        |
| Playwright   | [Getting started with Playwright](https://playwright.dev/docs/writing-tests)                                         |
| Puppeteer    | [Getting started with Puppeteer](https://pptr.dev)                                                                   |

----
url: https://angular.dev/api/core/NgModuleRef
----

```
    
      abstract class NgModuleRef<T> {}
    
    
```

The injector that contains all of the providers of the [`NgModule`](/api/core/NgModule).

The [`NgModule`](/api/core/NgModule) instance.

Destroys the module instance and all of the data structures associated with it.

@returns`void`

Registers a callback to be executed when the module is destroyed.

@paramcallback`() => void`

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/mergeApplicationConfig
----

```
function mergeApplicationConfig(
  ...configs: ApplicationConfig[]
): ApplicationConfig;
```

@paramconfigs`ApplicationConfig[]`

Two or more configurations to be merged.

@returns`ApplicationConfig`

A merged [ApplicationConfig](api/core/ApplicationConfig).

Jump to details

----
url: https://angular.dev/api/forms/signals/ItemType
----

```
    
      type ItemType<T extends Object> = T extends ReadonlyArray<any> ? T[number] : T[keyof T]
    
    
```

Jump to details

----
url: https://angular.dev/api/animations/keyframes
----

```
function keyframes(
  steps: AnimationStyleMetadata[],
): AnimationKeyframesSequenceMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramsteps`AnimationStyleMetadata[]`

A set of animation styles with optional offset data. The optional `offset` value for a style specifies a percentage of the total animation time at which that style is applied.

@returns`AnimationKeyframesSequenceMetadata`

An object that encapsulates the keyframes data.

## [Usage Notes](#usage-notes)

Use with the [`animate()`](/api/animations/animate) call. Instead of applying animations from the current state to the destination state, keyframes describe how each style entry is applied and at what point within the animation arc. Compare [CSS Keyframe Animations](https://www.w3schools.com/css/css3_animations.asp).

### [Usage](#usage)

In the following example, the offset values describe when each `backgroundColor` value is applied. The color is red at the start, and changes to blue when 20% of the total time has elapsed.

```
// the provided offset values
animate("5s", keyframes([
  style({ backgroundColor: "red", offset: 0 }),
  style({ backgroundColor: "blue", offset: 0.2 }),
  style({ backgroundColor: "orange", offset: 0.3 }),
  style({ backgroundColor: "black", offset: 1 })
]))
```

If there are no `offset` values specified in the style entries, the offsets are calculated automatically.

```
animate("5s", keyframes([
  style({ backgroundColor: "red" }) // offset = 0
  style({ backgroundColor: "blue" }) // offset = 0.33
  style({ backgroundColor: "orange" }) // offset = 0.66
  style({ backgroundColor: "black" }) // offset = 1
]))
```

Jump to details

----
url: https://angular.dev/guide/aria/menubar
----

The menubar is a horizontal navigation bar that provides persistent access to application menus. Menubars organize commands into logical categories like File, Edit, and View, helping users discover and execute application features through keyboard or mouse interaction.

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="material-menubar" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
  border-radius: 1rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="retro-menubar" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

## [Usage](#usage)

Menubars work well for organizing application commands into persistent, discoverable navigation.

**Use menubars when:**

* Building application command bars (such as File, Edit, View, Insert, Format)
* Creating persistent navigation that stays visible across the interface
* Organizing commands into logical top-level categories
* Need horizontal menu navigation with keyboard support
* Building desktop-style application interfaces

**Avoid menubars when:**

* Building dropdown menus for individual actions (use [Menu with trigger](guide/aria/menu) instead)
* Creating context menus (use [Menu](guide/aria/menu) guide pattern)
* Simple standalone action lists (use [Menu](guide/aria/menu) instead)
* Mobile interfaces where horizontal space is limited
* Navigation belongs in a sidebar or header navigation pattern

## [Features](#features)

* **Horizontal navigation** - Left/Right arrow keys move between top-level categories
* **Persistent visibility** - Always visible, not modal or dismissable
* **Hover-to-open** - Submenus open on hover after first keyboard or click interaction
* **Nested submenus** - Support multiple levels of menu depth
* **Keyboard navigation** - Arrow keys, Enter/Space, Escape, and typeahead search
* **Disabled states** - Disable entire menubar or individual items
* **RTL support** - Automatic right-to-left language navigation

## [Examples](#examples)

### [Basic menubar](#basic-menubar)

A menubar provides persistent access to application commands organized into top-level categories. Users navigate between categories with Left/Right arrows and open menus with Enter or Down arrow.

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="material-menubar" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
  border-radius: 1rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="retro-menubar" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

Press Right arrow to move between File, Edit, and View. Press Enter or Down arrow to open a menu and navigate submenu items with Up/Down arrows.

### [Disabled menubar items](#disabled-menubar-items)

Disable specific menu items or the entire menubar to prevent interaction. Control whether disabled items can receive keyboard focus with the `softDisabled` input.

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="material-menubar" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
  border-radius: 1rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="retro-menubar" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_right</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

When `[softDisabled]="true"` on the menubar, disabled items can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation.

### [RTL support](#rtl-support)

Menubars automatically adapt to right-to-left (RTL) languages. Arrow key navigation reverses direction, and submenus position on the left side.

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
import {Dir} from '@angular/cdk/bidi';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Dir, MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar dir="rtl" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_left</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
import {Dir} from '@angular/cdk/bidi';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Dir, MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="material-menubar" dir="rtl" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_left</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -6},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
  border-radius: 1rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {MenuBar, Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';
import {Dir} from '@angular/cdk/bidi';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Dir, MenuBar, Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  fileMenu = viewChild<Menu<string>>('fileMenu');
  shareMenu = viewChild<Menu<string>>('shareMenu');
  editMenu = viewChild<Menu<string>>('editMenu');
  viewMenu = viewChild<Menu<string>>('viewMenu');
  insertMenu = viewChild<Menu<string>>('insertMenu');
  imageMenu = viewChild<Menu<string>>('imageMenu');
  chartMenu = viewChild<Menu<string>>('chartMenu');
  formatMenu = viewChild<Menu<string>>('formatMenu');
  textMenu = viewChild<Menu<string>>('textMenu');
  sizeMenu = viewChild<Menu<string>>('sizeMenu');
  paragraphMenu = viewChild<Menu<string>>('paragraphMenu');
  alignMenu = viewChild<Menu<string>>('alignMenu');

  rendered = signal(false);

  onFocusIn() {
    this.rendered.set(true);
  }
}
```

### app.html

```
<div ngMenuBar class="retro-menubar" dir="rtl" (focusin)="onFocusIn()">
  <div
    ngMenuItem
    #fileEl
    #fileItem="ngMenuItem"
    class="menu-bar-item"
    value="File"
    [submenu]="fileMenu()"
  >
    File
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: fileEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #fileMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="New">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >article</span
          >
          <span class="label">New</span>
          <span class="shortcut">⌘N</span>
        </div>

        <div ngMenuItem value="Open">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >folder</span
          >
          <span class="label">Open</span>
          <span class="shortcut">⌘O</span>
        </div>

        <div ngMenuItem value="Make a copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >file_copy</span
          >
          <span class="label">Make a copy</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem #shareEl value="Share" [submenu]="shareMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >person_add</span
          >
          <span class="label">Share</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!fileItem.expanded()"
          [cdkConnectedOverlay]="{origin: shareEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #shareMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Share with others">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >person_add</span
                >
                <span class="label">Share with others</span>
              </div>

              <div ngMenuItem value="Publish to web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >public</span
                >
                <span class="label">Publish to web</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Download">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >download</span
          >
          <span class="label">Download</span>
        </div>

        <div ngMenuItem value="Print">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >print</span
          >
          <span class="label">Print</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Rename">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
          <span class="label">Rename</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Move to trash</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #editEl
    #editItem="ngMenuItem"
    class="menu-bar-item"
    value="Edit"
    [submenu]="editMenu()"
  >
    Edit
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: editEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #editMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Undo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">undo</span>
          <span class="label">Undo</span>
          <span class="shortcut">⌘Z</span>
        </div>

        <div ngMenuItem value="Redo">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">redo</span>
          <span class="label">Redo</span>
          <span class="shortcut">⌘Y</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Cut">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_cut</span
          >
          <span class="label">Cut</span>
          <span class="shortcut">⌘X</span>
        </div>

        <div ngMenuItem value="Copy">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_copy</span
          >
          <span class="label">Copy</span>
          <span class="shortcut">⌘C</span>
        </div>

        <div ngMenuItem value="Paste">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >content_paste</span
          >
          <span class="label">Paste</span>
          <span class="shortcut">⌘V</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Find and replace">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >find_replace</span
          >
          <span class="label">Find and replace</span>
          <span class="shortcut">⇧⌘H</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #viewEl
    #viewItem="ngMenuItem"
    class="menu-bar-item"
    value="View"
    [submenu]="viewMenu()"
  >
    View
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: viewEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #viewMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem value="Show print layout" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show print layout</span>
        </div>

        <div ngMenuItem value="Show ruler" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >check</span
          >
          <span class="label">Show ruler</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Zoom in">
          <span class="label">Zoom in</span>
          <span class="shortcut">⌘+</span>
        </div>

        <div ngMenuItem value="Zoom out">
          <span class="label">Zoom out</span>
          <span class="shortcut">⌘-</span>
        </div>

        <div role="separator" class="example-menu-item-separator"></div>

        <div ngMenuItem value="Full screen">
          <span class="label">Full screen</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #insertEl
    #insertItem="ngMenuItem"
    class="menu-bar-item"
    value="Insert"
    [submenu]="insertMenu()"
  >
    Insert
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: insertEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #insertMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #imageEl value="Image" [submenu]="imageMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >image</span
          >
          <span class="label">Image</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: imageEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #imageMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Upload from computer">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >upload</span
                >
                <span class="label">Upload from computer</span>
              </div>

              <div ngMenuItem value="Search the web">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >search</span
                >
                <span class="label">Search the web</span>
              </div>

              <div ngMenuItem value="By URL">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >link</span
                >
                <span class="label">By URL</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Table">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >table_chart</span
          >
          <span class="label">Table</span>
        </div>

        <div ngMenuItem #chartEl value="Chart" [submenu]="chartMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >insert_chart</span
          >
          <span class="label">Chart</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!insertItem.expanded()"
          [cdkConnectedOverlay]="{origin: chartEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #chartMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bar">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >bar_chart</span
                >
                <span class="label">Bar</span>
              </div>

              <div ngMenuItem value="Column">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >insert_chart</span
                >
                <span class="label">Column</span>
              </div>

              <div ngMenuItem value="Line">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >show_chart</span
                >
                <span class="label">Line</span>
              </div>

              <div ngMenuItem value="Pie">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >pie_chart</span
                >
                <span class="label">Pie</span>
              </div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem value="Horizontal line">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >horizontal_rule</span
          >
          <span class="label">Horizontal line</span>
        </div>
      </ng-template>
    </div>
  </ng-template>

  <div
    ngMenuItem
    #formatEl
    #formatItem="ngMenuItem"
    class="menu-bar-item"
    value="Format"
    [submenu]="formatMenu()"
  >
    Format
  </div>

  <ng-template
    [cdkConnectedOverlayOpen]="rendered()"
    [cdkConnectedOverlay]="{origin: formatEl, usePopover: 'inline'}"
    [cdkConnectedOverlayPositions]="[
      {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
    ]"
    [cdkConnectedOverlayDisableClose]="true"
    cdkAttachPopoverAsChild
  >
    <div ngMenu #formatMenu="ngMenu">
      <ng-template ngMenuContent>
        <div ngMenuItem #textEl #textItem="ngMenuItem" value="Text" [submenu]="textMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_bold</span
          >
          <span class="label">Text</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: textEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #textMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Bold">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_bold</span
                >
                <span class="label">Bold</span>
                <span class="shortcut">⌘B</span>
              </div>

              <div ngMenuItem value="Italic">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_italic</span
                >
                <span class="label">Italic</span>
                <span class="shortcut">⌘I</span>
              </div>

              <div ngMenuItem value="Underline">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_underlined</span
                >
                <span class="label">Underline</span>
                <span class="shortcut">⌘U</span>
              </div>

              <div ngMenuItem value="Strikethrough">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >strikethrough_s</span
                >
                <span class="label">Strikethrough</span>
                <span class="shortcut">⇧⌘X</span>
              </div>

              <div role="separator" class="example-menu-item-separator"></div>

              <div ngMenuItem #sizeEl value="Size" [submenu]="sizeMenu()">
                <span class="label">Size</span>
                <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
                  >arrow_left</span
                >
              </div>

              <ng-template
                [cdkConnectedOverlayOpen]="!!textItem.expanded()"
                [cdkConnectedOverlay]="{origin: sizeEl, usePopover: 'inline'}"
                [cdkConnectedOverlayPositions]="[
                  {
                    originX: 'end',
                    originY: 'top',
                    overlayY: 'top',
                    overlayX: 'start',
                    offsetX: -12,
                  },
                ]"
                [cdkConnectedOverlayDisableClose]="true"
                cdkAttachPopoverAsChild
              >
                <div ngMenu #sizeMenu="ngMenu">
                  <ng-template ngMenuContent>
                    <div ngMenuItem value="Increase font size">
                      <span class="label">Increase font size</span>
                      <span class="shortcut">⇧⌘.</span>
                    </div>

                    <div ngMenuItem value="Decrease font size">
                      <span class="label">Decrease font size</span>
                      <span class="shortcut">⇧⌘,</span>
                    </div>
                  </ng-template>
                </div>
              </ng-template>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #paragraphEl value="Paragraph styles" [submenu]="paragraphMenu()">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_align_justify</span
          >
          <span class="label">Paragraph styles</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: paragraphEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #paragraphMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Normal text">Normal text</div>
              <div ngMenuItem value="Heading 1">Heading 1</div>
              <div ngMenuItem value="Heading 2">Heading 2</div>
            </ng-template>
          </div>
        </ng-template>

        <div ngMenuItem #alignEl [submenu]="alignMenu()" value="Align & indent">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >format_indent_increase</span
          >
          <span class="label">Align & indent</span>
          <span class="icon arrow material-symbols-outlined" translate="no" aria-hidden="true"
            >arrow_left</span
          >
        </div>

        <ng-template
          [cdkConnectedOverlayOpen]="!!formatItem.expanded()"
          [cdkConnectedOverlay]="{origin: alignEl, usePopover: 'inline'}"
          [cdkConnectedOverlayPositions]="[
            {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: -12},
          ]"
          [cdkConnectedOverlayDisableClose]="true"
          cdkAttachPopoverAsChild
        >
          <div ngMenu #alignMenu="ngMenu">
            <ng-template ngMenuContent>
              <div ngMenuItem value="Align left">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_left</span
                >
                <span class="label">Align left</span>
              </div>

              <div ngMenuItem value="Align center">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_center</span
                >
                <span class="label">Align center</span>
              </div>

              <div ngMenuItem value="Align right">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_right</span
                >
                <span class="label">Align right</span>
              </div>

              <div ngMenuItem value="Justify">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >format_align_justify</span
                >
                <span class="label">Justify</span>
              </div>
            </ng-template>
          </div>
        </ng-template>
      </ng-template>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuBar] {
  display: flex;
  gap: 0.25rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

The `dir="rtl"` attribute enables RTL mode. Left arrow moves right, Right arrow moves left, maintaining natural navigation for RTL language users.

## [Testing](#testing)

Angular Aria provides component harnesses for testing menubar components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness} from '@angular/aria/menu/testing';
import {MyMenubarComponent} from './my-menubar'; // Your component

describe('MyMenubarComponent', () => {
  let fixture: ComponentFixture<MyMenubarComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyMenubarComponent],
    });

    fixture = TestBed.createComponent(MyMenubarComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should interact with menubar items', async () => {
    // Load the menubar harness (which is a MenuHarness with selector '[ngMenuBar]')
    const menubar = await loader.getHarness(MenuHarness.with({selector: '[ngMenuBar]'}));

    // Menubars are persistent and always "open"
    expect(await menubar.isOpen()).toBe(true);
    expect(await menubar.isMenuBar()).toBe(true);

    // Get top-level items
    const items = await menubar.getItems();
    expect(items.length).toBe(2);
    expect(await items[0].getText()).toBe('File');
    expect(await items[1].getText()).toBe('Edit');

    // Click an item to open its dropdown menu
    await items[0].click();

    const fileMenu = await items[0].getSubmenu();
    expect(fileMenu).toBeTruthy();
    expect(await fileMenu!.isOpen()).toBe(true);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`MenuBar`](/api/aria/menu/MenuBar)
* [`MenuItem`](/api/aria/menu/MenuItem)
* [`MenuTrigger`](/api/aria/menu/MenuTrigger)
* [`Menu`](/api/aria/menu/Menu)

----
url: https://angular.dev/api/platform-browser/BrowserModule
----

```
    
      class BrowserModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/RouterHashLocationFeature
----

```
    
      type RouterHashLocationFeature = RouterFeature<RouterFeatureKind.RouterHashLocationFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/core/OnInit
----

```
    
      interface OnInit {}
    
    
```

A callback method that is invoked immediately after the default change detector has checked the directive's data-bound properties for the first time, and before any of the view or content children have been checked. It is invoked only once when the directive is instantiated.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define its own initialization method.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements OnInit {
        ngOnInit() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/api/ssr/ServerRouteServer
----

```
    
      interface ServerRouteServer extends ServerRouteCommon {}
    
    
```

Specifies that the route uses Server-Side Rendering (SSR) mode.

The path associated with this route.

Optional additional headers to include in the response for this route.

Optional status code to return for this route.

Jump to details

----
url: https://angular.dev/errors/NG02200
----

[`NgFor`](/api/common/NgFor) could not find an iterable differ for the value passed in. Make sure it's an iterable, like an `Array`.

## [Debugging the error](#debugging-the-error)

When using ngFor in a template, you must use some type of Iterable, like `Array` or `Set`. If you're trying to iterate over the keys in an object or the entries of a `Map`, you should look at the [KeyValue pipe](/api/common/KeyValuePipe) instead.

----
url: https://angular.dev/essentials/next-steps
----

# Next Steps

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/next-steps.md "Edit this page")

Now that you have been introduced to the main concepts of Angular - you're ready to put what you learned into practice with our interactive tutorials and learn more with our in-depth guides.

## [Playground](#playground)

Try out Angular in an interactive code editor to further explore the concepts you've learned.

## [Tutorials](#tutorials)

Put these main concepts into practice with our in-browser tutorial or build your first app locally with the Angular CLI.

## [In-depth Guides](#in-depth-guides)

Here are some in-depth guides you might be interested in reading:

To see the rest of our in-depth guides, check out the main navigation.

----
url: https://angular.dev/guide/i18n/add-package
----

To take advantage of the localization features of Angular, use the [Angular CLI](cli "CLI Overview and Command Reference | Angular") to add the `@angular/localize` package to your project.

To add the `@angular/localize` package, use the following command to update the `package.json` and TypeScript configuration files in your project.

```
ng add @angular/localize
```

It adds `types: ["@angular/localize"]` in the TypeScript configuration files. It also adds line `/// <reference types="@angular/localize" />` at the top of the `main.ts` file which is the reference to the type definition.

**HELPFUL:** For more information about `package.json` and `tsconfig.json` files, see [Workspace npm dependencies](reference/configs/npm-packages "Workspace npm dependencies | Angular") and [TypeScript Configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html "TypeScript Configuration"). To learn about Triple-slash Directives visit [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-).

If `@angular/localize` is not installed and you try to build a localized version of your project (for example, while using the `i18n` attributes in templates), the [Angular CLI](cli "CLI Overview and Command Reference | Angular") will generate an error, which would contain the steps that you can take to enable i18n for your project.

## [Options](#options)

| OPTION             | DESCRIPTION                                                                                                                                                                                                                   | VALUE TYPE | DEFAULT VALUE |
| :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :------------ |
| `--project`        | The name of the project.                                                                                                                                                                                                      | `string`   |               |
| `--use-at-runtime` | If set, then [`$localize`](/api/localize/init/$localize) can be used at runtime. Also `@angular/localize` gets included in the `dependencies` section of `package.json`, rather than `devDependencies`, which is the default. | `boolean`  | `false`       |

For more available options, see `ng add` in [Angular CLI](cli "CLI Overview and Command Reference | Angular").

## [What's next](#whats-next)

----
url: https://angular.dev/guide/templates/expression-syntax
----

Angular expressions are based on JavaScript, but differ in some key ways. This guide walks through the similarities and differences between Angular expressions and standard JavaScript.

## [Value literals](#value-literals)

Angular supports a subset of [literal values](https://developer.mozilla.org/en-US/docs/Glossary/Literal) from JavaScript.

### [Supported value literals](#supported-value-literals)

| Literal type           | Example values                  |
| ---------------------- | ------------------------------- |
| String                 | `'Hello'`, `"World"`            |
| Boolean                | `true`, `false`                 |
| Number                 | `123`, `3.14`                   |
| Object                 | `{name: 'Alice'}`               |
| Array                  | `['Onion', 'Cheese', 'Garlic']` |
| null                   | `null`                          |
| RegExp                 | `/\d+/`                         |
| Template string        | `` `Hello ${name}` ``           |
| Tagged template string | `` tag`Hello ${name}` ``        |

### [Unsupported value literals](#unsupported-value-literals)

| Literal type | Example values |
| ------------ | -------------- |
| BigInt       | `1n`           |

## [Globals](#globals)

Angular expressions support the following [globals](https://developer.mozilla.org/en-US/docs/Glossary/Global_object):

* [undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined)
* [$any](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any)

No other JavaScript globals are supported. Common JavaScript globals include `Number`, `Boolean`, `NaN`, `Infinity`, `parseInt`, and more.

## [Local variables](#local-variables)

Angular automatically makes special local variables available for use in expressions in specific contexts. These special variables always start with the dollar sign character (`$`).

For example, `@for` blocks make several local variables corresponding to information about the loop, such as `$index`.

## [What operators are supported?](#what-operators-are-supported)

### [Supported operators](#supported-operators)

Angular supports the following operators from standard JavaScript.

| Operator                      | Example(s)                                     |
| ----------------------------- | ---------------------------------------------- |
| Add / Concatenate             | `1 + 2`                                        |
| Subtract                      | `52 - 3`                                       |
| Multiply                      | `41 * 6`                                       |
| Divide                        | `20 / 4`                                       |
| Remainder (Modulo)            | `17 % 5`                                       |
| Exponentiation                | `10 ** 3`                                      |
| Parenthesis                   | `9 * (8 + 4)`                                  |
| Conditional (Ternary)         | `a > b ? true : false`                         |
| And (Logical)                 | `&&`                                           |
| Or (Logical)                  | `\|\|`                                         |
| Not (Logical)                 | `!`                                            |
| Nullish Coalescing            | `possiblyNullValue ?? 'default'`               |
| Comparison Operators          | `<`, `<=`, `>`, `>=`, `==`, `===`, `!==`, `!=` |
| Unary Negation                | `-x`                                           |
| Unary Plus                    | `+y`                                           |
| Property Accessor             | `person['name']`                               |
| typeof                        | `typeof 42`                                    |
| void                          | `void 1`                                       |
| in                            | `'model' in car`                               |
| instanceof                    | `car instanceof Automobile`                    |
| Assignment                    | `a = b`                                        |
| Addition Assignment           | `a += b`                                       |
| Subtraction Assignment        | `a -= b`                                       |
| Multiplication Assignment     | `a *= b`                                       |
| Division Assignment           | `a /= b`                                       |
| Remainder Assignment          | `a %= b`                                       |
| Exponentiation Assignment     | `a **= b`                                      |
| Logical AND Assignment        | `a &&= b`                                      |
| Logical OR Assignment         | `a \|\|= b`                                    |
| Nullish Coalescing Assignment | `a ??= b`                                      |
| Spread in object literals     | `{...obj, foo: 'bar'}`                         |
| Spread in array literals      | `[...arr, 1, 2, 3]`                            |
| Rest in function calls        | `fn(...args)`                                  |
| Arrow function                | `items.filter((i) => i.active)`                |

**NOTE:** Assignment and increment/decrement operators are only valid in [event listener statements](#event-listener-statements). Using them in a binding or interpolation is a compile error, unless they are inside an arrow function.

Arrow functions must have an expression body. Pipes cannot be used inside an arrow function, but you can pass an arrow function as an argument to a pipe.

Angular expressions additionally also support the following non-standard operators:

| Operator                        | Example(s)                     |
| ------------------------------- | ------------------------------ |
| [Pipe](/guide/templates/pipes)  | `{{ total \| currency }}`      |
| Optional chaining\*             | `someObj.someProp?.nestedProp` |
| Non-null assertion (TypeScript) | `someObj!.someProp`            |

### [Safe navigation migration](#safe-navigation-migration)

Prior to Angular 22, the optional chaining operator (`?.`) returned `null` when the left-hand side is `null` or `undefined`, whereas standard JavaScript's `?.` returns `undefined`. Since Angular 22, the optional chaining operator behavior in angular expressions is alligned with the standard Javascript's behavior.

During the migration to v22, the `ng update` schematics added a `$safeNavigationMigration` magic function to existing expressions to preserve the previous `null`-returning behavior.

```
{{ $safeNavigationMigration(foo?.bar) }}
```

`$safeNavigationMigration` is a **temporary migration aid only**. It instructs the compiler to compile the wrapped safe-navigation expression using the legacy null-returning semantics rather than the standard JavaScript `?.` semantics. It is not a real function and cannot be called from TypeScript.

**NOTE:** Prefer migrating expressions to no longer rely on `null` vs `undefined` distinctions so that `$safeNavigationMigration` can be removed. This function may be removed in a future version of Angular.

### [Unsupported operators](#unsupported-operators)

| Operator              | Example(s)                        |
| --------------------- | --------------------------------- |
| All bitwise operators | `&`, `&=`, `~`, `\|=`, `^=`, etc. |
| Object destructuring  | `const { name } = person`         |
| Array destructuring   | `const [firstItem] = items`       |
| Comma operator        | `x = (x++, x)`                    |
| new                   | `new Car()`                       |

## [Lexical context for expressions](#lexical-context-for-expressions)

Angular expressions are evaluated within the context of the component class as well as any relevant [template variables](/guide/templates/variables), locals, and globals.

When referring to component class members, `this` is always implied. However, if a template declares a [template variables](guide/templates/variables) with the same name as a member, the variable shadows that member. You can unambiguously reference such a class member by explicitly using `this.`. This can be useful when creating an `@let` declaration that shadows a class member, e.g. for signal narrowing purposes.

## [Declarations](#declarations)

Generally speaking, declarations are not supported in Angular expressions. This includes, but is not limited to:

| Declarations                      | Example(s)                                  |
| --------------------------------- | ------------------------------------------- |
| Variables                         | `let label = 'abc'`, `const item = 'apple'` |
| Functions                         | `function myCustomFunction() { }`           |
| Arrow functions with block bodies | `() => { }`                                 |
| Classes                           | `class Rectangle { }`                       |

## [Event listener statements](#event-listener-statements)

Event handlers are **statements** rather than expressions. While they support all of the same syntax as Angular expressions, there are two key differences:

1. Statements **do support** assignment operators (but not destructuring assignments)
2. Statements **do not support** pipes

----
url: https://angular.dev/extended-diagnostics/NG8021
----

This diagnostic detects unreachable or redundant triggers in `@defer` blocks.

```
import {Component} from '@angular/core';

@Component({
  template: `
    @defer (on immediate; on timer(500ms)) {
      <large-component />
    }
  `,
})
class MyComponent {}
```

## [What's wrong with that?](#whats-wrong-with-that)

The diagnostic identifies several problematic patterns in defer trigger configuration that lead to:

* **Unnecessary code** that never affects behavior
* **Missed optimization opportunities** for better performance
* **Unreachable prefetch triggers** that will never execute

## [Diagnostic warning cases](#diagnostic-warning-cases)

This diagnostic flags the following problematic patterns:

### [`immediate` with prefetch triggers](#immediate-with-prefetch-triggers)

Avoid

### prefetch never runs

```
@Component({
  template: `
    @defer (on immediate; prefetch on idle) {
      <my-cmp />
    }
  `,
})
class MyComponent {}
```

Prefer

### remove redundant prefetch

```
@Component({
  template: `
    @defer (on immediate) {
      <my-cmp />
    }
  `,
})
class MyComponent {}
```

### [Prefetch timer not earlier than main timer](#prefetch-timer-not-earlier-than-main-timer)

Avoid

### prefetch is later than main

```
@Component({
  template: `
    @defer (on timer(100ms); prefetch on timer(3000ms)) {
      <my-cmp />
    }
  `,
})
class MyComponent {}
```

Avoid

### equal timing provides no benefit

```
@Component({
  template: `
    @defer (on timer(500ms); prefetch on timer(500ms)) {
      <my-cmp />
    }
  `,
})
class MyComponent {}
```

Prefer

### prefetch fires earlier

```
@Component({
  template: `
    @defer (on timer(1000ms); prefetch on timer(500ms)) {
      <large-component />
    }
  `,
})
class MyComponent {}
```

### [Prefetch without main triggers](#prefetch-without-main-triggers)

Avoid

### unexpected early fetch

```
@Component({
  template: `
    @defer (prefetch when someFlag()) {
      <heavy-comp />
    }
  `,
})
class MyComponent {
  someFlag = signal(false);
}
```

This configuration may suggest that the prefetch will only run when `someFlag` becomes true. However, since the main trigger still defaults to `on idle`, the deferred content can be fetched earlier during the browser’s idle period, effectively bypassing the intended condition.

Prefer

### explicitly define main trigger

```
@Component({
  template: `
    @defer (on interaction; prefetch when someFlag()) {
      <heavy-comp />
    }
  `,
})
class MyComponent {
  someFlag = signal(false);
}
```

### [Identical prefetch and main triggers](#identical-prefetch-and-main-triggers)

Avoid

### identical viewport trigger

```
@Component({
  template: `
    @defer (on viewport; prefetch on viewport) {
      <my-cmp />
    }
  `,
})
class MyComponent {}
```

Avoid

### identical interaction target

```
@Component({
  template: `
    <button #loadBtn>Load</button>
    @defer (on interaction(loadBtn); prefetch on interaction(loadBtn)) {
      <large-component />
    }
  `,
})
class MyComponent {}
```

Prefer

### remove redundant prefetch

```
@Component({
  template: `
    <button #loadBtn>Load</button>
    @defer (on interaction(loadBtn)) {
      <large-component />
    }
  `,
})
class MyComponent {}
```

Prefer

### use different targets for prefetch and main

```
@Component({
  template: `
    <div #hoverArea>Hover to prefetch</div>
    <button #clickBtn>Click to load</button>
    @defer (on interaction(clickBtn); prefetch on hover(hoverArea)) {
      <large-component />
    }
  `,
})
class MyComponent {}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `deferTriggerMisconfiguration` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "deferTriggerMisconfiguration": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/common/http/HttpFeatureKind
----

```
    
      enum HttpFeatureKind {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/ChildActivationStart
----

```
    
      class ChildActivationStart {}
    
    
```

@paramsnapshot`ActivatedRouteSnapshot`

@returns`ChildActivationStart`

Jump to details

----
url: https://angular.dev/api/ssr/node/CommonEngine
----

```
    
      class CommonEngine {}
    
    
```

@paramoptions`CommonEngineOptions | undefined`

@returns`CommonEngine`

Render an HTML document for a specific URL with specified render options

@paramopts`CommonEngineRenderOptions`

@returns`Promise<string>`

Jump to details

----
url: https://angular.dev/guide/routing/routing-with-urlmatcher
----

The Angular Router supports a powerful matching strategy that you can use to help users navigate your application. This matching strategy supports static routes, variable routes with parameters, wildcard routes, and so on. Also, build your own custom pattern matching for situations in which the URLs are more complicated.

In this tutorial, you'll build a custom route matcher using Angular's [`UrlMatcher`](/api/router/UrlMatcher). This matcher looks for an X (formerly Twitter) handle in the URL.

## [Objectives](#objectives)

Implement Angular's [`UrlMatcher`](/api/router/UrlMatcher) to create a custom route matcher.

## [Create a sample application](#create-a-sample-application)

Using the Angular CLI, create a new application, *angular-custom-route-match*. In addition to the default Angular application framework, you will also create a *profile* component.

1. Create a new Angular project, *angular-custom-route-match*.

   ```
   ng new angular-custom-route-match
   ```

   When prompted with `Would you like to add Angular routing?`, select `Y`.

   When prompted with `Which stylesheet format would you like to use?`, select `CSS`.

   After a few moments, a new project, `angular-custom-route-match`, is ready.

2. From your terminal, navigate to the `angular-custom-route-match` directory.

3. Create a component, *profile*.

   ```
   ng generate component profile
   ```

4. In your code editor, locate the file, `profile.html` and replace the placeholder content with the following HTML.

   ### profile.html

   ```
   <p>Hello {{ username() }}!</p>
   ```

5. In your code editor, locate the file, `app.html` and replace the placeholder content with the following HTML.

   ### app.html

   ```
   <h2>Routing with Custom Matching</h2>

   Navigate to <a routerLink="/@Angular">my profile</a>

   <router-outlet />
   ```

## [Configure your routes for your application](#configure-your-routes-for-your-application)

With your application framework in place, you next need to add routing capabilities to the `app.config.ts` file. As a part of this process, you will create a custom URL matcher that looks for an X handle in the URL. This handle is identified by a preceding `@` symbol.

1. In your code editor, open your `app.config.ts` file.

2. Add an `import` statement for Angular's [`provideRouter`](/api/router/provideRouter) and [`withComponentInputBinding`](/api/router/withComponentInputBinding) as well as the application routes.

   ```
   import {provideRouter, withComponentInputBinding} from '@angular/router';

   import {routes} from './app.routes';
   ```

3. In the providers array, add a `provideRouter(routes, withComponentInputBinding())` statement.

4. Define the custom route matcher by adding the following code to the application routes.

   ### app.routes.ts

   ```
   {
     matcher: (url) => {
       if (url.length === 1 && url[0].path.match(/^@[\w]+$/gm)) {
         return {consumed: url, posParams: {username: new UrlSegment(url[0].path.slice(1), {})}};
       }

       return null;
     },
     component: Profile,
   },
   ```

This custom matcher is a function that performs the following tasks:

* The matcher verifies that the array contains only one segment
* The matcher employs a regular expression to ensure that the format of the username is a match
* If there is a match, the function returns the entire URL, defining a `username` route parameter as a substring of the path
* If there isn't a match, the function returns null and the router continues to look for other routes that match the URL

**HELPFUL:** A custom URL matcher behaves like any other route definition. Define child routes or lazy loaded routes as you would with any other route.

## [Reading the route parameters](#reading-the-route-parameters)

With the custom matcher in place, you can now bind the route parameter in the `profile` component.

In your code editor, open your `profile.ts` file and create an `input` matching the `username` parameter. We added the [`withComponentInputBinding`](/api/router/withComponentInputBinding) feature earlier in [`provideRouter`](/api/router/provideRouter). This allows the [`Router`](/api/router/Router) to bind information directly to the route components.

```
username = input.required<string>();
```

## [Test your custom URL matcher](#test-your-custom-url-matcher)

With your code in place, you can now test your custom URL matcher.

1. From a terminal window, run the `ng serve` command.

   ```
   ng serve
   ```

2. Open a browser to `http://localhost:4200`.

   You should see a single web page, consisting of a sentence that reads `Navigate to my profile`.

3. Click the **my profile** hyperlink.

   A new sentence, reading `Hello, Angular!` appears on the page.

## [Next steps](#next-steps)

Pattern matching with the Angular Router provides you with a lot of flexibility when you have dynamic URLs in your application. To learn more about the Angular Router, see the following topics:

**HELPFUL:** This content is based on [Custom Route Matching with the Angular Router](https://medium.com/@brandontroberts/custom-route-matching-with-the-angular-router-fbdd48665483), by [Brandon Roberts](https://x.com/brandontroberts).

----
url: https://angular.dev/license
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

Copyright (c) 2010-2026 Google LLC. [https://angular.dev/license](/license)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

----
url: https://angular.dev/api/common/PathLocationStrategy
----

```
    
      class PathLocationStrategy extends LocationStrategy implements OnDestroy {}
    
    
```

@param\_platformLocation`PlatformLocation`

@paramhref`string | undefined`

@returns`PathLocationStrategy`

@paramfn`LocationChangeListener`

@returns`void`

@returns`string`

@paraminternal`string`

@returns`string`

@paramincludeHash`boolean`

@returns`string`

@returns`unknown`

@paramrelativePosition`number`

@returns`void`

## [Description](#description)

A [`LocationStrategy`](/api/common/LocationStrategy) used to configure the [`Location`](/api/common/Location) service to represent its state in the [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the browser's URL.

If you're using [`PathLocationStrategy`](/api/common/PathLocationStrategy), you may provide a [`APP_BASE_HREF`](/api/common/APP_BASE_HREF) or add a `<base href>` element to the document to override the default.

For instance, if you provide an [`APP_BASE_HREF`](/api/common/APP_BASE_HREF) of `'/my/app/'` and call `location.go('/foo')`, the browser's URL will become `example.com/my/app/foo`. To ensure all relative URIs resolve correctly, the `<base href>` and/or [`APP_BASE_HREF`](/api/common/APP_BASE_HREF) should end with a `/`.

Similarly, if you add `<base href='/my/app/'/>` to the document and call `location.go('/foo')`, the browser's URL will become `example.com/my/app/foo`.

Note that when using [`PathLocationStrategy`](/api/common/PathLocationStrategy), neither the query nor the fragment in the `<base href>` will be preserved, as outlined by the [RFC](https://tools.ietf.org/html/rfc3986#section-5.2.2).

To ensure that trailing slashes are always present or never present in the URL, use [`TrailingSlashPathLocationStrategy`](/api/common/TrailingSlashPathLocationStrategy) or [`NoTrailingSlashPathLocationStrategy`](/api/common/NoTrailingSlashPathLocationStrategy).

## [Usage Notes](#usage-notes)

### [Example](#example)

```
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {Component} from '@angular/core';

@Component({
  selector: 'path-location',
  providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}],
  template: `
    <h1>PathLocationStrategy</h1>
    Current URL is: <code>{{ location.path() }}</code
    ><br />
    Normalize: <code>/foo/bar/</code> is: <code>{{ location.normalize('foo/bar') }}</code
    ><br />
  `,
  standalone: false,
})
export class PathLocationComponent {
  location: Location;
  constructor(location: Location) {
    this.location = location;
  }
}
```

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/12-enable-routing
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/11-optimizing-images)[](/tutorials/learn-angular/13-define-a-route)

[](/tutorials/learn-angular/11-optimizing-images)[](/tutorials/learn-angular/13-define-a-route)

For most apps, there comes a point where the app requires more than a single page. When that time inevitably comes, routing becomes a big part of the performance story for users.

**NOTE:** Learn more about [routing in the in-depth guide](/guide/routing).

In this activity, you'll learn how to set up and configure your app to use Angular Router.

***

1. ### [Create an app.routes.ts file](#create-an-approutests-file)

   Inside `app.routes.ts`, make the following changes:

   1. Import [`Routes`](/api/router/Routes) from the `@angular/router` package.
   2. Export a constant called `routes` of type [`Routes`](/api/router/Routes), assign it `[]` as the value.

   ```
   import {Routes} from '@angular/router';

   export const routes: Routes = [];
   ```

2. ### [Add routing to provider](#add-routing-to-provider)

   In `app.config.ts`, configure the app to Angular Router with the following steps:

   1. Import the [`provideRouter`](/api/router/provideRouter) function from `@angular/router`.
   2. Import `routes` from the `./app.routes.ts`.
   3. Call the [`provideRouter`](/api/router/provideRouter) function with `routes` passed in as an argument in the `providers` array.

   ```
   import {ApplicationConfig} from '@angular/core';
   import {provideRouter} from '@angular/router';
   import {routes} from './app.routes';

   export const appConfig: ApplicationConfig = {
     providers: [provideRouter(routes)],
   };
   ```

3. ### [Import `RouterOutlet` in the component](#import-routeroutlet-in-the-component)

   Finally, to make sure your app is ready to use the Angular Router, you need to tell the app where you expect the router to display the desired content. Accomplish that by using the [`RouterOutlet`](/api/router/RouterOutlet) directive from `@angular/router`.

   Update the template for `App` by adding `<router-outlet />`

   ```
   @Component({
   ...
   template: `
       <nav>
         <a href="/">Home</a>
         |
         <a href="/user">User</a>
       </nav>
       <router-outlet />
     `,
   imports: [RouterOutlet],
   })
   export class App {}
   ```

Your app is now set up to use Angular Router. Nice work! 🙌

Keep the momentum going to learn the next step of defining the routes for our app.

----
url: https://angular.dev/api/forms/signals/SchemaPathRules
----

```
    
      type SchemaPathRules = SchemaPathRules.Supported | SchemaPathRules.Unsupported
    
    
```

Jump to details

----
url: https://angular.dev/cli/new
----

```
ng new [name] [options]
```

```
ng n [name] [options]
```

Creates and initializes a new Angular application that is the default project for a new workspace.

Provides interactive prompts for optional configuration, such as adding routing support. All prompts can safely be allowed to default.

* The new workspace folder is given the specified project name, and contains configuration files at the top level.

* By default, the files for a new initial application (with the same name as the workspace) are placed in the `src/` subfolder.

* The new application's configuration appears in the `projects` section of the `angular.json` workspace configuration file, under its project name.

* Subsequent applications that you generate in the workspace reside in the `projects/` subfolder.

If you plan to have multiple applications in the workspace, you can create an empty workspace by using the `--no-create-application` option. You can then use `ng generate application` to create an initial application. This allows a workspace name different from the initial app name, and ensures that all applications reside in the `/projects` subfolder, matching the structure of the configuration file.

## [Arguments](#arguments)

`name`

The name for the new workspace and the initial project. This name will be used for the root directory and various identifiers throughout the project.

Value Type`string`

## [Options](#options)

`ai-config`

Specifies which AI tools to generate configuration files for. These file are used to improve the outputs of AI tools by following the best practices.

Value Type`array`Allowed Values`claude-code`, `cursor`, `gemini-cli`, `none`, `open-ai-codex`, `vscode`

`collection`

Alias`c`

A collection of schematics to use in generating the initial application.

Value Type`string`

`commit`

Configure the initial Git commit for the new repository.

Value Type`boolean`Default`true`

`create-application`

Create a new initial application project in the new workspace. When false, creates an empty workspace with no initial application. You can then use the `ng generate application` command to create applications in the `projects` directory.

Value Type`boolean`Default`true`

`defaults`

Disable interactive input prompts for options with a default.

Value Type`boolean`Default`false`

`directory`

The directory where the new workspace and project should be created. If not specified, the workspace will be created in the current directory.

Value Type`string`

`dry-run`

Alias`d`

Run through and reports activity without writing out results.

Value Type`boolean`Default`false`

`file-name-style-guide`

The file naming convention to use for generated files. The '2025' style guide (default) uses a concise format (e.g., `app.ts` for the root component), while the '2016' style guide includes the type in the file name (e.g., `app.component.ts`). For more information, see the Angular Style Guide ([https://angular.dev/style-guide](/style-guide)).

Value Type`string`Allowed Values`2016`, `2025`Default`2025`

`force`

Force overwriting of existing files.

Value Type`boolean`Default`false`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`inline-style`

Alias`s`

Include the styles for the initial application's root component directly within the `app.ts` file. By default, a separate stylesheet file (e.g., `app.css`) is created.

Value Type`boolean`

`inline-template`

Alias`t`

Include the HTML template for the initial application's root component directly within the `app.ts` file. By default, a separate template file (e.g., `app.html`) is created.

Value Type`boolean`

`interactive`

Enable interactive input prompts.

Value Type`boolean`Default`true`

`minimal`

Generate a minimal Angular workspace without any testing frameworks. This is intended for learning purposes and simple experimentation, not for production applications.

Value Type`boolean`Default`false`

`new-project-root`

The path where new projects will be created within the workspace, relative to the workspace root. By default, new projects are created in the `projects` directory.

Value Type`string`Default`projects`

`package-manager`

The package manager used to install dependencies.

Value Type`string`Allowed Values`bun`, `npm`, `pnpm`, `yarn`

`prefix`

Alias`p`

The prefix to apply to generated selectors for the initial project. For example, if the prefix is `my-app` and you generate a component named `my-component`, the selector will be `my-app-my-component`.

Value Type`string`Default`app`

`routing`

Enable routing in the initial application project. This sets up the necessary files and modules for managing navigation between different views in your application.

Value Type`boolean`

`skip-git`

Alias`g`

Do not initialize a Git repository in the new workspace. By default, a Git repository is initialized to help you track changes to your project.

Value Type`boolean`Default`false`

`skip-install`

Skip the automatic installation of packages. You will need to manually install the dependencies later.

Value Type`boolean`Default`false`

`skip-tests`

Alias`S`

Skip the generation of unit test files `spec.ts`.

Value Type`boolean`Default`false`

`ssr`

Configure the initial application for Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering).

Value Type`boolean`

`standalone`

Creates an application based upon the standalone API, without NgModules.

Value Type`boolean`Default`true`

`strict`

Enable stricter type checking and stricter bundle budgets settings. This setting helps improve maintainability and catch bugs ahead of time. For more information, see [https://angular.dev/tools/cli/template-typecheck#strict-mode](/tools/cli/template-typecheck#strict-mode)

Value Type`boolean`Default`true`

`style`

The type of stylesheet files to be created for components in the initial project.

Value Type`string`Allowed Values`css`, `less`, `sass`, `scss`, `tailwind`

`test-runner`

The unit testing runner to use.

Value Type`string`Allowed Values`karma`, `vitest`Default`vitest`

`view-encapsulation`

Sets the view encapsulation mode for components in the initial project. This determines how component styles are scoped and applied. Options include: `Emulated` (default, styles are scoped to the component), `None` (styles are global), and `ShadowDom` (styles are encapsulated using Shadow DOM).

Value Type`string`Allowed Values`Emulated`, `None`, `ShadowDom`

`zoneless`

Create an initial application that does not utilize `zone.js`.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/api/core/RESPONSE_INIT
----

## [API](/api/core/RESPONSE_INIT#api)

```
    
      const RESPONSE_INIT: InjectionToken<ResponseInit | null>;
    
    
```

## [Description](/api/core/RESPONSE_INIT#description)

Injection token for response initialization options.

Use this token to provide response options for configuring or initializing HTTP responses in server-side rendering or API endpoints.

## [Usage Notes](/api/core/RESPONSE_INIT#usage-notes)

This token may be `null` in the following scenarios:

* During the build processes.
* When the application is rendered in the browser (client-side rendering).
* When performing static site generation (SSG).
* During route extraction in development (at the time of the request).

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CDK_DROP_LIST
----

```
    
      const CDK_DROP_LIST: any;
    
    
```

Jump to details

----
url: https://angular.dev/api/animations/browser/testing/MockAnimationPlayer
----

```
    
      class MockAnimationPlayer extends NoopAnimationPlayer {}
    
    
```

@paramelement`any`

@paramkeyframes`ɵStyleDataMap[]`

@paramduration`number`

@paramdelay`number`

@parameasing`string`

@parampreviousPlayers`any[]`

@returns`MockAnimationPlayer`

@returns`void`

@returns`void`

@returns`void`

@returns`void`

@returns`boolean`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/ResolveEnd
----

```
    
      class ResolveEnd extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramurlAfterRedirects`string`

@paramstate`RouterStateSnapshot`

@returns`ResolveEnd`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/api/service-worker/ServiceWorkerModule
----

```
    
      class ServiceWorkerModule {}
    
    
```

Register the given Angular Service Worker script.

If `enabled` is set to `false` in the given options, the module will behave as if service workers are not supported by the browser, and the service worker will not be registered.

@paramscript`string`

@paramoptions`SwRegistrationOptions`

@returns`ModuleWithProviders<ServiceWorkerModule>`

Jump to details

----
url: https://angular.dev/api/forms/signals/LimitKey
----

```
    
      type LimitKey<TLimit> = MetadataKey<  Signal<NonNullable<TLimit> | undefined>,  NonNullable<TLimit> | undefined,  NonNullable<TLimit> | undefined>
    
    
```

Jump to details

----
url: https://angular.dev/api/router/createUrlTreeFromSnapshot
----

```
function createUrlTreeFromSnapshot(
  relativeTo: ActivatedRouteSnapshot,
  commands: readonly any[],
  queryParams?: Params | null,
  fragment?: string | null,
  urlSerializer?: DefaultUrlSerializer,
): UrlTree;
```

@paramrelativeTo`ActivatedRouteSnapshot`

The [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) to apply the commands to

@paramcommands`readonly any[]`

An array of URL fragments with which to construct the new URL tree. If the path is static, can be the literal URL string. For a dynamic path, pass an array of path segments, followed by the parameters for each segment. The fragments are applied to the one provided in the `relativeTo` parameter.

@paramqueryParams`Params | null`

The query parameters for the [`UrlTree`](/api/router/UrlTree). `null` if the [`UrlTree`](/api/router/UrlTree) does not have any query parameters.

@paramfragment`string | null`

The fragment for the [`UrlTree`](/api/router/UrlTree). `null` if the [`UrlTree`](/api/router/UrlTree) does not have a fragment.

@paramurlSerializer`DefaultUrlSerializer`

The [`UrlSerializer`](/api/router/UrlSerializer) to use for handling query parameter normalization. You should provide your application's custom [`UrlSerializer`](/api/router/UrlSerializer) if one is configured to parse and serialize query parameter values to and from objects other than strings/string arrays.

@returns`UrlTree`

## [Usage Notes](#usage-notes)

```
// create /team/33/user/11
createUrlTreeFromSnapshot(snapshot, ['/team', 33, 'user', 11]);

// create /team/33;expand=true/user/11
createUrlTreeFromSnapshot(snapshot, ['/team', 33, {expand: true}, 'user', 11]);

// you can collapse static segments like this (this works only with the first passed-in value):
createUrlTreeFromSnapshot(snapshot, ['/team/33/user', userId]);

// If the first segment can contain slashes, and you do not want the router to split it,
// you can do the following:
createUrlTreeFromSnapshot(snapshot, [{segmentPath: '/one/two'}]);

// create /team/33/(user/11//right:chat)
createUrlTreeFromSnapshot(snapshot, ['/team', 33, {outlets: {primary: 'user/11', right:
'chat'}}], null, null);

// remove the right secondary node
createUrlTreeFromSnapshot(snapshot, ['/team', 33, {outlets: {primary: 'user/11', right: null}}]);

// For the examples below, assume the current URL is for the `/team/33/user/11` and the
`ActivatedRouteSnapshot` points to `user/11`:

// navigate to /team/33/user/11/details
createUrlTreeFromSnapshot(snapshot, ['details']);

// navigate to /team/33/user/22
createUrlTreeFromSnapshot(snapshot, ['../22']);

// navigate to /team/44/user/22
createUrlTreeFromSnapshot(snapshot, ['../../team/44/user/22']);
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8114
----

This diagnostic detects cases where the nullish coalescing operator (`??`) is mixed with the logical or (`||`) or logical and (`&&`) operators without parentheses to disambiguate precedence.

```
import {Component, input} from '@angular/core';

@Component({
  template: `<button [disabled]="hasPermission() && task()?.disabled ?? true">Run</button>`,
})
class MyComponent {
  hasPermission = input(false);
  task = input<Task | undefined>(undefined);
}
```

## [What's wrong with that?](#whats-wrong-with-that)

Without disambiguating parentheses, its difficult to understand whether the `??` or `||`/`&&` is evaluated first. This is considered an error in TypeScript and JavaScript, but has historically been allowed in Angular templates.

## [What should I do instead?](#what-should-i-do-instead)

Always use parentheses to disambiguate in theses situations. If you're unsure what the original intent of the code was but want to keep the behavior the same, place the parentheses around the `??` operator.

```
import {Component, input} from '@angular/core';

@Component({
  template: `<button [disabled]="hasPermission() && (task()?.disabled ?? true)">Run</button>`,
})
class MyComponent {
  hasPermission = input(false);
  task = input<Task | undefined>(undefined);
}
```

----
url: https://angular.dev/guide/routing/testing
----

Testing routing and navigation is essential to ensure your application behaves correctly when users navigate between different routes. This guide covers various strategies for testing routing functionality in Angular applications.

## [Prerequisites](/guide/routing/testing#prerequisites)

This guide assumes you are familiar with the following tools and libraries:

* **[Vitest](https://vitest.dev/)** - JavaScript testing framework that provides the testing syntax (`describe`, `it`, `expect`)
* **[Angular Testing Utilities](guide/testing)** - Angular's built-in testing tools ([`TestBed`](api/core/testing/TestBed), [`ComponentFixture`](api/core/testing/ComponentFixture))
* **[`RouterTestingHarness`](api/router/testing/RouterTestingHarness)** - Test harness for testing routed components with built-in navigation and component testing capabilities

## [Testing scenarios](/guide/routing/testing#testing-scenarios)

### [Route parameters](/guide/routing/testing#route-parameters)

Components often rely on route parameters from the URL to fetch data, like a user ID for a profile page.

The following example shows how to test a `UserProfile` component that displays a user ID from the route.

### user-profile.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {provideRouter, Router} from '@angular/router';
import {RouterTestingHarness} from '@angular/router/testing';
import {UserProfile} from './user-profile';

describe('UserProfile', () => {
  it('should display user ID from route parameters', async () => {
    TestBed.configureTestingModule({
      imports: [UserProfile],
      providers: [provideRouter([{path: 'user/:id', component: UserProfile}])],
    });

    const harness = await RouterTestingHarness.create();
    await harness.navigateByUrl('/user/123', UserProfile);

    expect(harness.routeNativeElement?.textContent).toContain('User Profile: 123');
  });
});
```

### user-profile.ts

```
import {Component, inject} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  template: '<h1>User Profile: {{userId}}</h1>',
})
export class UserProfile {
  private route = inject(ActivatedRoute);
  userId: string | null = this.route.snapshot.paramMap.get('id');
}
```

### [Route guards](/guide/routing/testing#route-guards)

Route guards control access to routes based on conditions like authentication or permissions. When testing guards, focus on mocking dependencies and verifying navigation outcomes.

The following example tests an `authGuard` that allows navigation for authenticated users and redirects unauthenticated users to a login page.

### auth.guard.spec.ts

```
import {describe, expect, it, vi, type Mocked} from 'vitest';
import {RouterTestingHarness} from '@angular/router/testing';
import {provideRouter, Router} from '@angular/router';
import {authGuard} from './auth.guard';
import {AuthStore} from './auth-store';
import {Component} from '@angular/core';
import {TestBed} from '@angular/core/testing';

@Component({template: '<h1>Protected Page</h1>'})
class Protected {}

@Component({template: '<h1>Login Page</h1>'})
class Login {}

describe('authGuard', () => {
  let authStore: Mocked<AuthStore>;
  let harness: RouterTestingHarness;

  async function setup(isAuthenticated: boolean) {
    authStore = {isAuthenticated: vi.fn().mockReturnValue(isAuthenticated)} as Mocked<AuthStore>;

    TestBed.configureTestingModule({
      providers: [
        {provide: AuthStore, useValue: authStore},
        provideRouter([
          {path: 'protected', component: Protected, canActivate: [authGuard]},
          {path: 'login', component: Login},
        ]),
      ],
    });

    harness = await RouterTestingHarness.create();
  }

  it('allows navigation when user is authenticated', async () => {
    await setup(true);
    await harness.navigateByUrl('/protected', Protected);
    // The protected component should render when authenticated
    expect(harness.routeNativeElement?.textContent).toContain('Protected Page');
  });

  it('redirects to login when user is not authenticated', async () => {
    await setup(false);
    await harness.navigateByUrl('/protected', Login);
    // The login component should render after redirect
    expect(harness.routeNativeElement?.textContent).toContain('Login Page');
  });
});
```

### auth.guard.ts

```
import {inject} from '@angular/core';
import {CanActivateFn, Router} from '@angular/router';
import {AuthStore} from './auth-store';

export const authGuard: CanActivateFn = () => {
  const authStore = inject(AuthStore);
  const router = inject(Router);
  return authStore.isAuthenticated() ? true : router.parseUrl('/login');
};
```

### [Router outlets](/guide/routing/testing#router-outlets)

Router outlet tests are more of an integration test since you're essentially testing the integration between the [`Router`](api/router/Router), the outlet, and the components being displayed.

Here's an example of how to set up a test that verifies different components are displayed for different routes:

### app.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {RouterTestingHarness} from '@angular/router/testing';
import {provideRouter} from '@angular/router';
import {Component} from '@angular/core';
import {App} from './app';

@Component({
  template: '<h1>Home Page</h1>',
})
class MockHome {}

@Component({
  template: '<h1>About Page</h1>',
})
class MockAbout {}

describe('App Router Outlet', () => {
  let harness: RouterTestingHarness;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [App],
      providers: [
        provideRouter([
          {path: '', component: MockHome},
          {path: 'about', component: MockAbout},
        ]),
      ],
    });

    harness = await RouterTestingHarness.create();
  });

  it('should display home component for default route', async () => {
    await harness.navigateByUrl('');

    expect(harness.routeNativeElement?.textContent).toContain('Home Page');
  });

  it('should display about component for about route', async () => {
    await harness.navigateByUrl('/about');

    expect(harness.routeNativeElement?.textContent).toContain('About Page');
  });
});
```

### app.ts

```
import {Component} from '@angular/core';
import {RouterOutlet, RouterLink} from '@angular/router';

@Component({
  imports: [RouterOutlet, RouterLink],
  template: `
    <nav>
      <a routerLink="/">Home</a>
      <a routerLink="/about">About</a>
    </nav>
    <router-outlet />
  `,
})
export class App {}
```

### [Nested routes](/guide/routing/testing#nested-routes)

Testing nested routes ensures that both the parent and child components render correctly when navigating to nested URLs. This is important because nested routes involve multiple layers.

You need to verify that:

1. The parent component renders properly.
2. The child component renders within it.
3. Ensure that both components can access their respective route data.

Here's an example of testing a parent-child route structure:

### nested-routes.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {RouterTestingHarness} from '@angular/router/testing';
import {provideRouter} from '@angular/router';
import {Parent, Child} from './nested-components';

describe('Nested Routes', () => {
  let harness: RouterTestingHarness;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [Parent, Child],
      providers: [
        provideRouter([
          {
            path: 'parent',
            component: Parent,
            children: [{path: 'child', component: Child}],
          },
        ]),
      ],
    });

    harness = await RouterTestingHarness.create();
  });

  it('should render parent and child components for nested route', async () => {
    await harness.navigateByUrl('/parent/child');

    expect(harness.routeNativeElement?.textContent).toContain('Parent Component');
    expect(harness.routeNativeElement?.textContent).toContain('Child Component');
  });
});
```

### nested.ts

```
import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';

@Component({
  imports: [RouterOutlet],
  template: `
    <h1>Parent Component</h1>
    <router-outlet />
  `,
})
export class Parent {}

@Component({
  template: '<h2>Child Component</h2>',
})
export class Child {}
```

### [Query parameters and fragments](/guide/routing/testing#query-parameters-and-fragments)

Query parameters (like `?search=angular&category=web`) and URL fragments (like `#section1`) provide additional data through the URL that doesn't affect which component loads, but does affect how the component behaves. Components that read query parameters through [`ActivatedRoute.queryParams`](api/router/ActivatedRoute#queryParams) need to be tested to ensure they handle different parameter scenarios correctly.

Unlike route parameters that are part of the route definition, query parameters are optional and can change without triggering route navigation. This means you need to test both the initial loading and the reactive updates when query parameters change.

Here's an example of how to test query parameters and fragments:

### search.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {Router, provideRouter} from '@angular/router';
import {RouterTestingHarness} from '@angular/router/testing';
import {Search} from './search';

describe('Search', () => {
  let component: Search;
  let harness: RouterTestingHarness;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [Search],
      providers: [provideRouter([{path: 'search', component: Search}])],
    });

    harness = await RouterTestingHarness.create();
  });

  it('should read search term from query parameters', async () => {
    component = await harness.navigateByUrl('/search?q=angular', Search);

    expect(component.searchTerm()).toBe('angular');
  });
});
```

### search.ts

```
import {Component, inject, computed} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {toSignal} from '@angular/core/rxjs-interop';

@Component({
  template: '<div>Search term: {{searchTerm()}}</div>',
})
export class Search {
  private route = inject(ActivatedRoute);
  private queryParams = toSignal(this.route.queryParams, {initialValue: {}});

  searchTerm = computed(() => this.queryParams()['q'] || null);
}
```

## [Best practices for router testing](/guide/routing/testing#best-practices-for-router-testing)

1. **Use RouterTestingHarness** - For testing routed components, use [`RouterTestingHarness`](api/router/testing/RouterTestingHarness) which provides a cleaner API and eliminates the need for test host components. It offers direct component access, built-in navigation, and better type safety. However, it isn't as suitable for some scenarios, such as testing named outlets, where you may need to create custom host components.
2. **Handle external dependencies thoughtfully** - Prefer real implementations when possible for more realistic tests. If real implementations aren't feasible (e.g., external APIs), use fakes that approximate the real behavior. Use mocks or stubs only as a last resort, as they can make tests brittle and less reliable.
3. **Test navigation state** - Verify both the navigation action and the resulting application state, including URL changes and component rendering.
4. **Handle asynchronous operations** - Router navigation is asynchronous. Use `async/await` to properly handle timing in your tests.
5. **Test error scenarios** - Include tests for invalid routes, failed navigation, and guard rejections to ensure your application handles edge cases gracefully.
6. **Do not mock Angular Router** - Instead, provide real route configurations and use the harness to navigate. This makes your tests more robust and less likely to break on internal Angular updates, while also ensuring you catch real issues when the router updates since mocks can hide breaking changes.

----
url: https://angular.dev/api/common/NgIfContext
----

The ngIf directive is deprecated in favor of the @if block instead.

## [API](#api)

```
    
      class NgIfContext<T = unknown> {}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/upgrade/LOCATION_UPGRADE_CONFIGURATION
----

```
    
      const LOCATION_UPGRADE_CONFIGURATION: InjectionToken<LocationUpgradeConfig>;
    
    
```

Jump to details

----
url: https://angular.dev/guide/signals/resource
----

All signal APIs are synchronous— [`signal`](/api/core/signal), [`computed`](/api/core/computed), `input`, etc. However, applications often need to deal with data that is available asynchronously. A [`Resource`](/api/core/Resource) gives you a way to incorporate async data into your application's signal-based code and still allow you to access its data synchronously.

You can use a [`Resource`](/api/core/Resource) to perform any kind of async operation, but the most common use-case for [`Resource`](/api/core/Resource) is fetching data from a server. The following example creates a resource to fetch some user data.

The easiest way to create a [`Resource`](/api/core/Resource) is the [`resource`](/api/core/resource) function.

```
import {computed, resource, Signal} from '@angular/core';

const userId: Signal<string> = getUserId();

const userResource = resource({
  // Define a reactive computation.
  // The params value recomputes whenever any read signals change.
  params: () => ({id: userId()}),

  // Define an async loader that retrieves data.
  // The resource calls this function every time the `params` value changes.
  loader: ({params}) => fetchUser(params),
});

// Create a computed signal based on the result of the resource's loader function.
const firstName = computed(() => {
  if (userResource.hasValue()) {
    // `hasValue` serves 2 purposes:
    // - It acts as type guard to strip `undefined` from the type
    // - It protects against reading a throwing `value` when the resource is in error state
    return userResource.value().firstName;
  }

  // fallback in case the resource value is `undefined` or if the resource is in error state
  return undefined;
});
```

The [`resource`](/api/core/resource) function accepts a [`ResourceOptions`](/api/core/ResourceOptions) object with two main properties: `params` and `loader`.

The `params` property defines a reactive computation that produces a parameter value. Whenever signals read in this computation change, the resource produces a new parameter value, similar to [`computed`](/api/core/computed).

The `loader` property defines a [`ResourceLoader`](/api/core/ResourceLoader)— an async function that retrieves some state. The resource calls the loader every time the `params` computation produces a new value, passing that value to the loader. See [Resource loaders](#resource-loaders) below for more details.

[`Resource`](/api/core/Resource) has a `value` signal that contains the results of the loader.

## [Resource loaders](#resource-loaders)

When creating a resource, you specify a [`ResourceLoader`](/api/core/ResourceLoader). This loader is an async function that accepts a single parameter— a [`ResourceLoaderParams`](/api/core/ResourceLoaderParams) object— and returns a value.

The [`ResourceLoaderParams`](/api/core/ResourceLoaderParams) object contains three properties: `params`, `previous`, and `abortSignal`.

| Property      | Description                                                                                                                                      |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `params`      | The value of the resource's `params` computation.                                                                                                |
| `previous`    | An object with a `status` property, containing the previous [`ResourceStatus`](/api/core/ResourceStatus).                                        |
| `abortSignal` | An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal). See [Aborting requests](#aborting-requests) below for details. |

If the `params` computation returns `undefined`, the loader function does not run and the resource status becomes `'idle'`.

### [Streaming resources](#streaming-resources)

Some asynchronous data sources produce multiple values over time instead of returning a single result. Examples include WebSockets, Server-Sent Events (SSE), and Firestore `onSnapshot` listeners.

Use `stream` for these continuously updating data sources. Unlike `loader`, which resolves once for each request, `stream` returns a signal whose value can continue to update as new data becomes available.

Use `loader` for one-time asynchronous operations, such as fetching data from an HTTP endpoint.

In this example, `userUpdates` simulates an external source such as a WebSocket or SSE connection.

```
const userUpdates = signal({value: 'Alice'});

const userResource = resource({
  stream: () => userUpdates,
});

// Simulate a new value arriving from the external data source:
userUpdates.set({value: 'Bob'});
```

### [Aborting requests](#aborting-requests)

A resource aborts an outstanding loading operation if the `params` computation changes while the resource is loading.

You can use the `abortSignal` in [`ResourceLoaderParams`](/api/core/ResourceLoaderParams) to respond to aborted requests. For example, the native `fetch` function accepts an `AbortSignal`:

```
const userId: Signal<string> = getUserId();

const userResource = resource({
  params: () => ({id: userId()}),
  loader: ({params, abortSignal}): Promise<User> => {
    // fetch cancels any outstanding HTTP requests when the given `AbortSignal`
    // indicates that the request has been aborted.
    return fetch(`users/${params.id}`, {signal: abortSignal}).then((res) => res.json());
  },
});
```

See [`AbortSignal` on MDN](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) for more details on request cancellation with `AbortSignal`.

### [Reloading](#reloading)

You can programmatically trigger a resource's `loader` by calling the `reload` method.

```
const userId: Signal<string> = getUserId();

const userResource = resource({
  params: () => ({id: userId()}),
  loader: ({params}) => fetchUser(params),
});

// ...

userResource.reload();
```

## [Resource status](#resource-status)

The resource object has several signal properties for reading the status of the asynchronous loader.

| Property    | Description                                                                                                     |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| `value`     | The most recent value of the resource, or `undefined` if no value has been received.                            |
| `hasValue`  | Whether the resource has a value.                                                                               |
| `error`     | The most recent error encountered while running the resource's loader, or `undefined` if no error has occurred. |
| `isLoading` | Whether the resource loader is currently running.                                                               |
| `status`    | The resource's specific [`ResourceStatus`](/api/core/ResourceStatus), as described below.                       |

The `status` signal provides a specific [`ResourceStatus`](/api/core/ResourceStatus) that describes the state of the resource using a string constant.

| Status        | `value()`         | Description                                                                  |
| ------------- | :---------------- | ---------------------------------------------------------------------------- |
| `'idle'`      | `undefined`       | The resource has no valid request and the loader has not run.                |
| `'error'`     | Throws an error   | The loader has encountered an error.                                         |
| `'loading'`   | `undefined`       | The loader is running as a result of the `params` value changing.            |
| `'reloading'` | Previous value    | The loader is running as a result of calling the resource's `reload` method. |
| `'resolved'`  | Resolved value    | The loader has completed.                                                    |
| `'local'`     | Locally set value | The resource's value has been set locally via `.set()` or `.update()`        |

You can use this status information to conditionally display user interface elements, such as loading indicators and error messages.

## [Caching `resource` data with SSR](#caching-resource-data-with-ssr)

When an application renders on the server, a resource loader runs once to produce the initial HTML. During hydration, the browser normally runs the same loader again.

To reuse the server result, provide an `id` for the resource. Angular stores the resolved value in [`TransferState`](/api/core/TransferState) on the server and uses it on the client to initialize the resource in a `'resolved'` state.

```
const userId: Signal<string> = getUserId();

const userResource = resource({
  params: () => ({id: userId()}),
  loader: ({params}) => fetchUser(params),
  id: 'user-unique-id',
});
```

The `id` value must be unique within your application and identical on the server and the client so that Angular can match the cached entry to the resource that requested it.

**IMPORTANT:** Because the cached value is serialized into the page's HTML, avoid setting `id` on resources that load data specific to the user who triggered the server-side render, especially if the rendered HTML can be cached or shared between users.

## [Chaining resources](#chaining-resources)

Sometimes one resource depends on the result of another. You can express this dependency using the `chain` function available in the `params` context object.

```
import {resource} from '@angular/core';

const userResource = resource({
  params: () => ({id: getUserId()}),
  loader: ({params}) => fetchUser(params),
});

const companyResource = resource({
  params: ({chain}) => chain(userResource)?.companyId,
  loader: ({params: companyId}) => fetchCompany(companyId),
});
```

Here `companyResource` depends on the user's `companyId`, which is only known once `userResource` has loaded. `chain(userResource)` reads the value of `userResource` and automatically propagates its status to `companyResource`:

* If `userResource` is **idle**, `companyResource` also becomes `idle`.
* If `userResource` is **loading** or **reloading**, `companyResource` enters the `loading` state and its loader does not run. Note that during `reloading`, `chain` does not return the previously resolved value.
* If `userResource` is in an **error** state, `companyResource` also enters the `error` state.
* If `userResource` is **resolved** or **local**, `chain` returns its current value, which `companyResource` then uses as its params.

When `chain` propagates a status from `userResource` (`idle`, `loading`, `reloading`, or `error`), the params function does not continue. When `userResource` is `resolved` or `local`, `chain` returns its value, which can itself be `undefined`. The example handles this with `chain(userResource)?.companyId`, so an `undefined` value results in `undefined` params and `companyResource` becomes `idle`.

**NOTE:** Pass the chained value directly as the params value rather than wrapping it in an object. A params value like `{companyId: undefined}` is still a defined value, so the loader would run with an `undefined` `companyId` instead of the resource becoming `idle`.

### [Chaining vs. reading resource values directly](#chaining-vs-reading-resource-values-directly)

You might be tempted to read a resource's value directly inside `params`:

Avoid

### reads value() directly without status propagation

```
const companyResource = resource({
  params: () => {
    const user = userResource.value(); // may be undefined
    return user ? {companyId: user.companyId} : undefined;
  },
  loader: ({params}) => fetchCompany(params.companyId),
});
```

While this works, returning `undefined` from `params` makes the resource go `idle` rather than reflecting the actual state of the upstream resource. Using `chain` is preferred because it correctly mirrors `loading` and `error` states.

Reach for `chain` only when the downstream resource performs its own async work that depends on the upstream value. If you only need to derive a value synchronously from a resource, use [`computed`](/api/core/computed) instead.

## [Reactive data fetching with `httpResource`](#reactive-data-fetching-with-httpresource)

[`httpResource`](/guide/http/http-resource) is a wrapper around [`HttpClient`](/api/common/http/HttpClient) that gives you the request status and response as signals. It makes HTTP requests through the Angular HTTP stack, including interceptors.

## [Resource composition with snapshots](#resource-composition-with-snapshots)

A [`ResourceSnapshot`](/api/core/ResourceSnapshot) is a structured representation of a resource's current state. Every resource has a `snapshot` property that provides a signal of its current state.

```
const userId: Signal<string> = getUserId();

const userResource = resource({
  params: () => ({id: userId()}),
  loader: ({params}) => fetchUser(params),
});

const userSnapshot = userResource.snapshot;
```

Each snapshot contains a `status` and either a `value` or an `error`.

### [Composing resources with snapshots](#composing-resources-with-snapshots)

You can create new resources from snapshots using [`resourceFromSnapshots`](/api/core/resourceFromSnapshots). This enables composition with signal APIs like [`computed`](/api/core/computed) and [`linkedSignal`](/api/core/linkedSignal) to transform resource behavior.

```
import {linkedSignal, resourceFromSnapshots, Resource, ResourceSnapshot} from '@angular/core';

function withPreviousValue<T>(input: Resource<T>): Resource<T> {
  const derived = linkedSignal<ResourceSnapshot<T>, ResourceSnapshot<T>>({
    source: input.snapshot,
    computation: (snap, previous) => {
      if (snap.status === 'loading' && previous && previous.value.status !== 'error') {
        // When the input resource enters loading state, we keep the value
        // from its previous state, if any.
        return {status: 'loading' as const, value: previous.value.value};
      }

      // Otherwise we simply forward the state of the input resource.
      return snap;
    },
  });

  return resourceFromSnapshots(derived);
}

@Component({/*... */})
export class AwesomeProfile {
  userId = input.required<number>();
  user = withPreviousValue(httpResource(() => `/user/${this.userId()}`));
  // When userId changes, user.value() keeps the old user data until the new one loads
}
```

----
url: https://angular.dev/api/common/http/HttpHandler
----

```
    
      abstract class HttpHandler {}
    
    
```

@paramreq`HttpRequest<any>`

@returns`Observable<HttpEvent<any>>`

## [Description](#description)

Transforms an [`HttpRequest`](/api/common/http/HttpRequest) into a stream of [`HttpEvent`](/api/common/http/HttpEvent)s, one of which will likely be a [`HttpResponse`](/api/common/http/HttpResponse).

[`HttpHandler`](/api/common/http/HttpHandler) is injectable. When injected, the handler instance dispatches requests to the first interceptor in the chain, which dispatches to the second, etc, eventually reaching the [`HttpBackend`](/api/common/http/HttpBackend).

In an [`HttpInterceptor`](/api/common/http/HttpInterceptor), the [`HttpHandler`](/api/common/http/HttpHandler) parameter is the next interceptor in the chain.

Jump to details

----
url: https://angular.dev/api/forms/NG_VALUE_ACCESSOR
----

```
    
      const NG_VALUE_ACCESSOR: InjectionToken<readonly ControlValueAccessor[]>;
    
    
```

## [Description](#description)

Used to provide a [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for form controls.

See [`DefaultValueAccessor`](/api/forms/DefaultValueAccessor) for how to implement one.

Jump to details

----
url: https://angular.dev/guide/forms/signals/async-operations
----

Some validation requires data from external sources like backend APIs or third-party services. Signal Forms provides two functions for asynchronous validation: [`validateHttp()`](/api/forms/signals/validateHttp) for HTTP-based validation and [`validateAsync()`](/api/forms/signals/validateAsync) for custom resource-based validation.

## [When to use async validation](#when-to-use-async-validation)

Use async validation when your validation logic requires external data. Some common examples include:

* **Uniqueness checks** - Verify usernames or emails don't already exist
* **Database lookups** - Check values against server-side data
* **External API validation** - Validate addresses, tax IDs, or other data with third-party services
* **Server-side business rules** - Apply validation rules that only the server can verify

Don't use async validation for checks you can perform synchronously on the client. Use synchronous validation rules like `pattern()`, `email()`, or [`validate()`](/api/forms/signals/validate) for format validation and static rules.

## [How async validation works](#how-async-validation-works)

Async validation runs only after all synchronous validation passes. While the validation executes, the field's `pending()` signal returns `true`. The validation can target errors to specific fields, and pending requests cancel automatically when field values change.

Here's an example checking username availability:

```
import {Component, signal} from '@angular/core';
import {form, validateHttp, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <form>
      <label>
        Username:
        <input [formField]="registrationForm.username" />
      </label>

      @if (registrationForm.username().pending()) {
        <span class="checking">Checking availability...</span>
      }
      @if (registrationForm.username().invalid()) {
        @for (error of registrationForm.username().errors(); track $index) {
          <span class="error">{{ error.message }}</span>
        }
      }
    </form>
  `,
})
export class Registration {
  registrationModel = signal({username: ''});

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateHttp(schemaPath.username, {
      request: ({value}) => {
        const username = value();
        return username ? `/api/users/check?username=${username}` : undefined;
      },
      onSuccess: (response: {available: boolean}) => {
        return response.available
          ? null
          : {
              kind: 'usernameTaken',
              message: 'Username is already taken',
            };
      },
      onError: (error) => {
        console.error('Validation request failed:', error);
        return {
          kind: 'serverError',
          message: 'Could not verify username availability',
        };
      },
    });
  });
}
```

The validation flow works like this:

1. User types a value
2. Synchronous validation rules run first
3. If synchronous validation fails, async validation doesn't run
4. If synchronous validation passes, async validation starts and `pending()` becomes `true`
5. The request completes and `pending()` becomes `false`
6. Errors update based on the response

## [HTTP validation with validateHttp()](#http-validation-with-validatehttp)

The [`validateHttp()`](/api/forms/signals/validateHttp) function provides the most common form of async validation. Use it when you need to validate against a REST API or any HTTP endpoint.

### [Request function](#request-function)

The `request` function returns either a URL string or an [`HttpResourceRequest`](/api/common/http/HttpResourceRequest) object. Return `undefined` to skip the validation:

```
import {Component, signal} from '@angular/core';
import {form, validateHttp, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `...`,
})
export class Registration {
  registrationModel = signal({username: ''});

  // Cache usernames that passed validation
  private validatedUsernames = new Set<string>();

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateHttp(schemaPath.username, {
      request: ({value}) => {
        const username = value();
        // Skip HTTP request if already validated
        if (this.validatedUsernames.has(username)) return undefined;

        return `/api/users/check?username=${username}`;
      },
      onSuccess: (response: {available: boolean}, {value}) => {
        if (response.available) {
          // Cache successful validations
          this.validatedUsernames.add(value());
          return null;
        }
        return {
          kind: 'usernameTaken',
          message: 'Username is already taken',
        };
      },
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username',
      }),
    });
  });
}
```

For POST requests or custom headers, return an [`HttpResourceRequest`](/api/common/http/HttpResourceRequest) object:

```
request: ({value}) => ({
  url: '/api/validate',
  method: 'POST',
  body: {username: value()},
}) // prettier-ignore
```

### [Success and error handlers](#success-and-error-handlers)

The `onSuccess` function receives the HTTP response and returns validation errors or `undefined` for valid values:

```
onSuccess: (response: { valid: boolean; message?: string }) => {
  if (response.valid) return undefined;

  return {
    kind: 'invalid',
    message: response.message || 'Validation failed',
  };
} // prettier-ignore
```

Return multiple errors when needed:

```
onSuccess: (response: { usernameTaken: boolean; profanity: boolean }) => {
  const errors = [];
  if (response.usernameTaken) {
    errors.push({
      kind: 'usernameTaken',
      message: 'Username taken',
    });
  }
  if (response.profanity) {
    errors.push({
      kind: 'profanity',
      message: 'Username contains inappropriate content',
    });
  }
  return errors.length > 0 ? errors : undefined;
} // prettier-ignore
```

The type for `onSuccess` can be specified either directly in the parameter, or with the `parse` property of [`validateHttp`](/api/forms/signals/validateHttp)'s `options`

```
onSuccess: (response: { usernameTaken: boolean; profanity: boolean }) => {
  // ...
} // prettier-ignore

// or

options: {
  parse: (response) => response as {usernameTaken: boolean; profanity: boolean};
}
onSuccess: (response) => {
  // ...
} // prettier-ignore
```

The `onError` function handles request failures like network errors or HTTP errors:

```
onError: (error) => {
  console.error('Validation request failed:', error);
  return {
    kind: 'serverError',
    message: 'Could not verify. Please try again later.',
  };
} // prettier-ignore
```

### [HTTP options](#http-options)

Customize the HTTP request by returning an [`HttpResourceRequest`](/api/common/http/HttpResourceRequest) object from the `request` function:

```
import {HttpHeaders} from '@angular/common/http';

validateHttp(schemaPath.field, {
  request: ({value}) => ({
    url: `/api/validate?value=${value()}`,
    headers: new HttpHeaders({
      Authorization: 'Bearer token',
    }),
    timeout: 5000,
  }),
  onSuccess: (response: {valid: boolean}) =>
    response.valid
      ? null
      : {
          kind: 'invalid',
          message: 'Invalid value',
        },
  onError: () => ({
    kind: 'requestFailed',
    message: 'Unable to reach server to validate.',
  }),
});
```

**TIP:** See the [httpResource API documentation](api/common/http/httpResource) for all available options.

## [Custom async validation with validateAsync()](#custom-async-validation-with-validateasync)

Most applications should use [`validateHttp()`](/api/forms/signals/validateHttp) for async validation. It handles HTTP requests with minimal configuration and covers the majority of use cases.

[`validateAsync()`](/api/forms/signals/validateAsync) is a lower-level API that exposes Angular's resource primitive directly. It offers complete control but requires more code and familiarity with Angular's resource API.

Consider [`validateAsync()`](/api/forms/signals/validateAsync) only when [`validateHttp()`](/api/forms/signals/validateHttp) can't meet your needs. Some examples include:

* **Non-HTTP validation** - WebSocket connections, IndexedDB lookups, or Web Worker computations
* **Custom caching strategies** - Application-specific caching beyond simple memoization
* **Complex retry logic** - Custom backoff strategies or conditional retries
* **Direct resource access** - When you need the full resource lifecycle

### [Creating a custom validation rule](#creating-a-custom-validation-rule)

The [`validateAsync()`](/api/forms/signals/validateAsync) function requires four properties: `params`, `factory`, `onSuccess`, and `onError`. The `params` function returns the parameters for your resource, while `factory` creates the resource:

```
import {Component, inject, signal, resource, Signal} from '@angular/core';
import {form, validateAsync, FormField} from '@angular/forms/signals';
import {UsernameValidator} from './username-validator';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `...`,
})
export class Registration {
  registrationModel = signal({username: ''});

  private usernameValidator = inject(UsernameValidator);
  private cache = new Map<string, {available: boolean}>();

  // Custom resource factory with caching
  createUsernameResource = (usernameSignal: Signal<string | undefined>) => {
    return resource({
      params: () => usernameSignal(),
      loader: async ({params: username}) => {
        if (!username) return undefined;

        // Check cache first
        const cached = this.cache.get(username);
        if (cached !== undefined) return cached;

        // Use injected service for validation
        const result = await this.usernameValidator.checkAvailability(username);

        // Cache result
        this.cache.set(username, result);
        return result;
      },
    });
  };

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => {
        const username = value();
        return username.length >= 3 ? username : undefined!;
      },
      factory: this.createUsernameResource,
      onSuccess: (result) => {
        return result?.available
          ? null
          : {
              kind: 'usernameTaken',
              message: 'Username taken',
            };
      },
      onError: (error) => {
        console.error('Validation failed:', error);
        return {
          kind: 'serverError',
          message: 'Could not verify username',
        };
      },
    });
  });
}
```

The `params` function runs on every value change. Return `undefined` to skip validation. The `factory` function runs once during setup and receives params as a signal. The resource updates automatically when params change.

### [Using Observable-based services](#using-observable-based-services)

If your application has existing services that return Observables, use [`rxResource`](/api/core/rxjs-interop/rxResource) from `@angular/core/rxjs-interop`:

```
import {Component, inject, signal, Signal} from '@angular/core';
import {rxResource} from '@angular/core/rxjs-interop';
import {form, validateAsync, FormField} from '@angular/forms/signals';
import {UsernameService} from './username-service';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `...`,
})
export class Registration {
  registrationModel = signal({username: ''});

  private usernameService = inject(UsernameService);

  private createUsernameResource = (usernameSignal: Signal<string | undefined>) => {
    return rxResource({
      params: () => usernameSignal(),
      stream: ({params: username}) => this.usernameService.checkUsername(username),
    });
  };

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => value(),
      factory: this.createUsernameResource,
      onSuccess: (result) =>
        result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'},
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username',
      }),
    });
  });
}
```

The [`rxResource`](/api/core/rxjs-interop/rxResource) function works directly with Observables and handles subscription cleanup automatically when the field value changes.

## [Debouncing](#debouncing)

The [`debounce`](/api/forms/signals/debounce) rule delays when a user's input is committed to the form model. You can think of it as the rule holding back values until the user pauses typing. This is useful when downstream behavior shouldn't react to every keystroke, such as expensive derived computations, validation that flashes errors mid-word, or search filters that reapply on each character.

Add the [`debounce`](/api/forms/signals/debounce) rule inside a schema to delay how a form field's UI changes reach the form model. In its simplest form, `debounce(path, ms)` holds each UI change for the given number of milliseconds before writing it to the model. A new change within that window resets the timer.

The following example applies [`debounce`](/api/forms/signals/debounce) and [`validateHttp`](/api/forms/signals/validateHttp) to the username field to delay the username availability check in a registration form until the user pauses typing:

```
import {Component, signal} from '@angular/core';
import {form, debounce, validateHttp, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <label>
      Username:
      <input [formField]="registrationForm.username" />
    </label>

    @if (registrationForm.username().pending()) {
      <span class="checking">Checking availability...</span>
    }
  `,
})
export class Registration {
  registrationModel = signal({username: ''});

  registrationForm = form(this.registrationModel, (schemaPath) => {
    // Hold UI updates for 300 ms before writing to the model
    debounce(schemaPath.username, 300);

    // Runs against the debounced model value, not every keystroke
    validateHttp(schemaPath.username, {
      request: ({value}) => {
        const username = value();
        // Skip the request for blank values
        return username ? `/api/users/check?username=${username}` : undefined;
      },
      onSuccess: (response: {available: boolean}) =>
        response.available ? null : {kind: 'usernameTaken', message: 'Username is already taken'},
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username availability',
      }),
    });
  });
}
```

With a 300 ms debounce, the model updates and validates only after the user pauses typing longer than the configured duration. For example, typing "signal forms" in a quick burst fires one validation request instead of twelve.

### [Touch flushes the model](#touch-flushes-the-model)

Regardless of the debounce duration, the framework writes the field's `controlValue()` to the model immediately when the field becomes touched. Native inputs become touched on blur, so a user who finishes typing and tabs away doesn't have to wait for the debounce timer to expire. Custom controls can mark the field as touched in response to any event they choose.

In the typical case, this matters for form submission. When the user clicks a submit button, the focused input blurs, which touches that field and flushes its pending debounce before the submission handler runs.

### [Commit only on blur](#commit-only-on-blur)

Some fields shouldn't update mid-typing at all, and instead should only update after the user has finished entering a value. For example, if you have a search filter that reapplies on every change or a form that triggers expensive derived state, it is often better for the model to wait until the user finishes typing.

In these scenarios, pass `'blur'` instead of a duration to defer all updates until the field becomes touched:

```
form(this.registrationModel, (schemaPath) => {
  debounce(schemaPath.username, 'blur');
});
```

With `'blur'`, the model keeps its previous value while the user is typing. Sync and async validation, derived signals, and any reactive rules reading the field all see the previous value until the field becomes touched. This commonly occurs when the user blurs a native input, or when a custom control signals touch on its own.

### [Custom timing logic](#custom-timing-logic)

For timing logic that a duration or `'blur'` can't express, pass a [`Debouncer`](/api/forms/signals/Debouncer) function. The function receives the field context and an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), and returns a `Promise<void>` that resolves when the model should update:

```
import {debounce, type Debouncer} from '@angular/forms/signals';

const shorterWhenLonger: Debouncer<string> = ({value}, abortSignal) => {
  // Shorter queries get a longer delay since the user is likely still typing.
  const ms = value().length < 3 ? 500 : 200;
  return new Promise((resolve) => {
    const timeoutId = setTimeout(resolve, ms);
    // Abort fires when this field is touched or its value changes, so the pending timer is cleared
    abortSignal.addEventListener(
      'abort',
      () => {
        clearTimeout(timeoutId);
        resolve();
      },
      {once: true},
    );
  });
};

const registrationForm = form(registrationModel, (schemaPath) => {
  debounce(schemaPath.username, shorterWhenLonger);
});
```

The `abortSignal` fires when the field is touched, or when its value changes before the debounce resolves. Resolve the promise on abort so your debouncer releases any pending timers. The framework writes the pending value to the model on touch, and discards it when a newer value arrives. See the [`debounce` API reference](api/forms/signals/debounce) for the full [`Debouncer`](/api/forms/signals/Debouncer) signature.

### [Debouncing a single async validator](#debouncing-a-single-async-validator)

The [`debounce`](/api/forms/signals/debounce) rule holds back every reaction to the field, from sync validation to derived signals to async validation. However, there are times when you want the opposite: cheap sync validators like `required` or `email` running immediately for instant feedback, while only the expensive async call waits for the user to settle. Both [`validateHttp()`](/api/forms/signals/validateHttp) and [`validateAsync()`](/api/forms/signals/validateAsync) accept their own [`debounce` option](api/forms/signals/validateAsync) that throttles just that validator:

```
form(this.registrationModel, (schemaPath) => {
  validateHttp(schemaPath.username, {
    // Throttles only this HTTP call
    debounce: 300,
    request: ({value}) => {
      const username = value();
      // Skip the request for blank values
      return username ? `/api/users/check?username=${username}` : undefined;
    },
    onSuccess: (response: {available: boolean}) =>
      response.available ? null : {kind: 'usernameTaken', message: 'Username is already taken'},
    onError: () => ({
      kind: 'serverError',
      message: 'Could not verify username availability',
    }),
  });
});
```

The model still updates on every keystroke, and any other rules attached to the field still react immediately. Only the HTTP request is debounced: each change waits 300 ms of quiet before firing, so a request only goes out once the user has paused typing.

Choose between the two layers based on scope:

| Option                                                        | When to use                                                                                                                         |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [`debounce()`](/api/forms/signals/debounce) rule              | Sync validation, derived state, and submission should all wait until the field commits. The whole field shouldn't react mid-typing. |
| `validateHttp({ debounce })` or `validateAsync({ debounce })` | Cheap sync validators should give immediate feedback, but expensive async calls should wait for the user to pause.                  |

Both options accept a duration in milliseconds. Their custom-timing callbacks differ: the form-level rule takes a [`Debouncer`](/api/forms/signals/Debouncer), and the validator-level option takes a [`DebounceTimer`](/api/core/DebounceTimer) from `@angular/core`. The two signatures are not interchangeable.

## [Composing resources in async validation with a factory](#composing-resources-in-async-validation-with-a-factory)

The built-in [`debounce` option](api/forms/signals/validateAsync) covers throttling, but [`validateAsync()`](/api/forms/signals/validateAsync) exposes a deeper composition point: the `factory` function. The factory receives the params as a signal and returns a resource. Between those two points, you're free to compose whatever you need.

In its simplest form, a factory wraps a single resource. A username-availability check can live as a method on the component class, and then be wired into [`validateAsync`](/api/forms/signals/validateAsync) by reference:

```
export class Registration {
  registrationModel = signal({username: ''});
  private usernameValidator = inject(UsernameValidator);

  // Factory function
  checkUsernameAvailable = (username: Signal<string | undefined>) =>
    resource({
      params: () => username(),
      loader: async ({params: name}) => this.usernameValidator.checkAvailability(name),
    });

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => {
        const username = value();
        // Skip validation for short usernames
        return username.length >= 3 ? username : undefined!;
      },
      debounce: 300,
      // Reference to the factory defined above
      factory: this.checkUsernameAvailable,
      onSuccess: (result) =>
        result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'},
      onError: () => ({kind: 'serverError', message: 'Could not verify'}),
    });
  });
}
```

The `params` callback returns `undefined` for short usernames, signaling that validation should skip. With `debounce: 300` applied, the resource waits until the user pauses typing for 300 ms before acting on each change. It then runs the loader for valid usernames and stays idle once the debounced value settles to `undefined`.

### [Combining debounce with additional logic](#combining-debounce-with-additional-logic)

When you need logic beyond a plain duration debounce, use a custom factory to combine debouncing with that logic. A common case is caching validated responses. For example, once the server has confirmed a username, you don't need to ask again on subsequent keystrokes that revisit the same value.

```
export class Registration {
  registrationModel = signal({username: ''});
  private usernameValidator = inject(UsernameValidator);

  registrationForm = form(this.registrationModel, (schemaPath) => {
    validateAsync(schemaPath.username, {
      params: ({value}) => {
        const username = value();
        return username.length >= 3 ? username : undefined;
      },
      factory: (username) => {
        // Core primitive: settles 300 ms after the source stops changing
        const debouncedUsername = debounced(username, 300);
        // Cache lives in the factory's closure and persists for the field's lifetime
        const cache = new Map<string, {available: boolean}>();
        return resource({
          // Read from the debounced signal, not the raw one
          params: () => debouncedUsername.value(),
          loader: async ({params: name}) => {
            const cached = cache.get(name);
            if (cached) return cached;

            const result = await this.usernameValidator.checkAvailability(name);
            cache.set(name, result);
            return result;
          },
        });
      },
      onSuccess: (result) =>
        result?.available ? null : {kind: 'usernameTaken', message: 'Username taken'},
      onError: () => ({
        kind: 'serverError',
        message: 'Could not verify username',
      }),
    });
  });
}
```

The `cache` lives in the factory's closure, so it persists for the field's lifetime. Once the user has typed a username the server has already checked, the loader reads from the cache instead of making a new network request.

## [Understanding pending state](#understanding-pending-state)

When async validation runs, the field's `pending()` signal returns `true`. During this time:

* `valid()` returns `false`
* `invalid()` returns `false`
* `errors()` returns an empty array
* [`submit()`](/api/forms/signals/submit) does not wait for it; by default the `action` runs anyway (see [`ignoreValidators`](guide/forms/signals/form-submission#controlling-validation-gating-with-ignorevalidators))

Show the pending state in your template to provide feedback:

```
<input [formField]="loginForm.username" />

@if (loginForm.username().pending()) {
  <span class="loading">Checking availability...</span>
}

@if (loginForm.username().touched() && loginForm.username().invalid()) {
  @for (error of loginForm.username().errors(); track $index) {
    <span class="error">{{ error.message }}</span>
  }
}
```

Disable form submission while validation is pending:

```
<button type="submit" [disabled]="loginForm().pending()">
  @if (loginForm().pending()) {
    Validating...
  } @else {
    Submit
  }
</button>
```

**TIP:** See the [Field State Management guide](guide/forms/signals/field-state-management) for more patterns using `pending()`, `valid()`, and `invalid()` signals.

### [Validation execution order](#validation-execution-order)

Async validation only runs after synchronous validation passes. This prevents unnecessary server requests for invalid input:

```
import {form, required, minLength, validateHttp} from '@angular/forms/signals';

form(model, (schemaPath) => {
  // 1. These synchronous validation rules run first
  required(schemaPath.username);
  minLength(schemaPath.username, 3);

  // 2. This async validation rule only runs if synchronous validation passes
  validateHttp(schemaPath.username, {
    request: ({value}) => `/api/check?username=${value()}`,
    onSuccess: (result: {valid: boolean}) =>
      result.valid
        ? null
        : {
            kind: 'usernameTaken',
            message: 'Username taken',
          },
    onError: () => ({
      kind: 'serverError',
      message: 'Validation failed',
    }),
  });
});
```

This execution order improves performance by reducing server load and catching format errors instantly.

### [Request cancellation](#request-cancellation)

When a field value changes, Signal Forms automatically cancels any pending async validation request for that field. This prevents race conditions and ensures validation always reflects the current value. You don't need to implement cancellation logic yourself.

## [Best practices](#best-practices)

### [Combine with synchronous validation](#combine-with-synchronous-validation)

Always validate format before making async requests. This catches errors instantly and prevents unnecessary server requests:

```
import {form, required, email, validateHttp} from '@angular/forms/signals';

form(model, (schemaPath) => {
  // Validate format first
  required(schemaPath.email);
  email(schemaPath.email);

  // Then check availability
  validateHttp(schemaPath.email, {
    request: ({value}) => `/api/emails/check?email=${value()}`,
    onSuccess: (result: {available: boolean}) =>
      result.available
        ? null
        : {
            kind: 'emailInUse',
            message: 'Email already in use',
          },
    onError: () => ({
      kind: 'serverError',
      message: 'Could not verify email',
    }),
  });
});
```

### [Skip validation when appropriate](#skip-validation-when-appropriate)

Return `undefined` from the `request` function to skip validation. Use this to avoid validating empty fields or values that don't meet minimum requirements:

```
import {validateHttp} from '@angular/forms/signals';

validateHttp(schemaPath.username, {
  request: ({value}) => {
    const username = value();
    // Skip validation for empty or short usernames
    if (!username || username.length < 3) return undefined;

    return `/api/users/check?username=${username}`;
  },
  onSuccess: (result: {valid: boolean}) =>
    result.valid
      ? null
      : {
          kind: 'usernameTaken',
          message: 'Username taken',
        },
  onError: () => ({
    kind: 'serverError',
    message: 'Validation failed',
  }),
});
```

### [Handle errors gracefully](#handle-errors-gracefully)

Provide clear, user-friendly error messages. Log technical details for debugging but show simple messages to users:

```
import {validateHttp} from '@angular/forms/signals';

validateHttp(schemaPath.field, {
  request: ({value}) => `/api/validate?field=${value()}`,
  onSuccess: (result: {valid: boolean; message?: string}) => {
    if (result.valid) return null;
    // Use server message when available
    return {
      kind: 'serverError',
      message: result.message || 'Validation failed',
    };
  },
  onError: (error) => {
    // Log for debugging
    console.error('Validation request failed:', error);

    // Show user-friendly message
    return {
      kind: 'serverError',
      message: 'Unable to validate. Please try again later.',
    };
  },
});
```

### [Show clear feedback](#show-clear-feedback)

Use the `pending()` signal to show when validation is happening. This helps users understand delays and provides better perceived performance:

```
@if (field().pending()) {
  <span class="checking">
    <span class="spinner"></span>
    Checking...
  </span>
}
@if (field().valid() && !field().pending()) {
  <span class="success">Available</span>
}
@if (field().invalid()) {
  <span class="error">{{ field().errors()[0]?.message }}</span>
}
```

## [Next steps](#next-steps)

This guide covered async validation with [`validateHttp()`](/api/forms/signals/validateHttp) and [`validateAsync()`](/api/forms/signals/validateAsync). Related guides explore other aspects of Signal Forms:

For detailed API documentation, see:

* [`validateHttp()`](api/forms/signals/validateHttp) - HTTP-based async validation
* [`validateAsync()`](api/forms/signals/validateAsync) - Custom resource-based async validation
* [`httpResource()`](api/common/http/httpResource) - Angular's HTTP resource API
* [`resource()`](api/core/resource) - Angular's resource primitive

----
url: https://angular.dev/api/cdk/drag-drop/DragConstrainPosition
----

```
    
      type DragConstrainPosition = (  userPointerPosition: Point,  dragRef: DragRef,  dimensions: DOMRect,  pickupPositionInElement: Point,) => Point
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/apply
----

```
function apply<TValue>(
  path: SchemaPath<TValue>,
  schema: NoInfer<SchemaOrSchemaFn<TValue>>,
): void;
```

@parampath`SchemaPath<TValue>`

The target path to apply the schema to.

@paramschema`NoInfer<SchemaOrSchemaFn<TValue>>`

The schema to apply to the property

@returns`void`

## [Usage Notes](#usage-notes)

```
const nameSchema = schema<{first: string, last: string}>((name) => {
  required(name.first);
  required(name.last);
});
const profileForm = form(signal({name: {first: '', last: ''}, age: 0}), (profile) => {
  apply(profile.name, nameSchema);
});
```

Jump to details

----
url: https://angular.dev/guide/aria/menu
----

A menu offers a list of actions or options to users, typically appearing in response to a button click or right-click. Menus support keyboard navigation with arrow keys, submenus, checkboxes, radio buttons, and disabled items.

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Mark as read">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >mark_email_read</span
        >
        <span class="label">Mark as read</span>
      </div>

      <div ngMenuItem value="Snooze">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
        <span class="label">Snooze</span>
      </div>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div
        ngMenuItem
        class="menu-item"
        value="Categorize"
        #categorizeItem
        [submenu]="categorizeMenu()"
      >
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >category</span
        >
        <span class="label">Categorize</span>
        <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Mark as important">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label_important</span
              >
              <span class="label">Mark as important</span>
            </div>

            <div ngMenuItem value="Star">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >star</span
              >
              <span class="label">Star</span>
            </div>

            <div ngMenuItem value="Label">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label</span
              >
              <span class="label">Label</span>
            </div>
          </ng-template>
        </div>
      </ng-template>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div ngMenuItem value="Archive">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >archive</span
        >
        <span class="label">Archive</span>
      </div>

      <div ngMenuItem value="Report spam">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
        <span class="label">Report spam</span>
      </div>

      <div ngMenuItem value="Delete">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  border-radius: 0.5rem;
  color: var(--primary-contrast);
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuTrigger]:not([disabled]):hover,
[ngMenuTrigger][aria-expanded='true'],
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div class="group">
        <div ngMenuItem value="Mark as read">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >mark_email_read</span
          >
          <span class="label">Mark as read</span>
        </div>

        <div ngMenuItem value="Snooze">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >snooze</span
          >
          <span class="label">Snooze</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Delete</span>
        </div>
      </div>

      <div class="group">
        <div
          ngMenuItem
          class="menu-item"
          value="Categorize"
          #categorizeItem
          [submenu]="categorizeMenu()"
        >
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >category</span
          >
          <span class="label">Categorize</span>
          <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div class="group">
              <div ngMenuItem value="Mark as important">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >label_important</span
                >
                <span class="label">Mark as important</span>
              </div>

              <div ngMenuItem value="Star">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >star</span
                >
                <span class="label">Star</span>
              </div>

              <div ngMenuItem value="Label">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >label</span
                >
                <span class="label">Label</span>
              </div>
            </div>
          </ng-template>
        </div>
      </ng-template>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  border-radius: 0.5rem;
  border: 1px solid transparent;
  background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
}

[ngMenu] .group {
  padding: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu] .group:first-of-type {
  border-top-left-radius: 1rem;
  border-top-right-radius: 1rem;
}

[ngMenu] .group:last-of-type {
  border-bottom-left-radius: 1rem;
  border-bottom-right-radius: 1rem;
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuTrigger]:hover,
[ngMenuTrigger][aria-expanded='true'] {
  background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()">
  Open Menu
</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Mark as read">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >mark_email_read</span
        >
        <span class="label">Mark as read</span>
      </div>

      <div ngMenuItem value="Snooze">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
        <span class="label">Snooze</span>
      </div>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div
        ngMenuItem
        class="menu-item"
        value="Categorize"
        #categorizeItem
        [submenu]="categorizeMenu()"
      >
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >category</span
        >
        <span class="label">Categorize</span>
        <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Mark as important">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label_important</span
              >
              <span class="label">Mark as important</span>
            </div>

            <div ngMenuItem value="Star">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >star</span
              >
              <span class="label">Star</span>
            </div>

            <div ngMenuItem value="Label">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label</span
              >
              <span class="label">Label</span>
            </div>
          </ng-template>
        </div>
      </ng-template>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div ngMenuItem value="Archive">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >archive</span
        >
        <span class="label">Archive</span>
      </div>

      <div ngMenuItem value="Report spam">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
        <span class="label">Report spam</span>
      </div>

      <div ngMenuItem value="Delete">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  font-family: 'Press Start 2P';
  color: #000;
  background-color: var(--vivid-pink);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngMenuTrigger]:hover {
  transform: translate(1px, 1px);
}

[ngMenuTrigger]:active {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuTrigger]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: 8px;
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

## [Usage](#usage)

Menus work well for presenting lists of actions or commands that users can choose from.

**Use menus when:**

* Building application command menus (File, Edit, View)
* Creating context menus (right-click actions)
* Showing dropdown action lists
* Implementing toolbar dropdowns
* Organizing settings or options

**Avoid menus when:**

* Building site navigation (use navigation landmarks instead)
* Creating form selects (use the [Select](guide/aria/select) component)
* Switching between content panels (use [Tabs](guide/aria/tabs))
* Showing collapsible content (use [Accordion](guide/aria/accordion))

## [Features](#features)

* **Keyboard navigation** - Arrow keys, Home/End, and character search for efficient navigation
* **Submenus** - Nested menu support with automatic positioning
* **Menu types** - Standalone menus, triggered menus, and menubars
* **Checkboxes and radios** - Toggle and selection menu items
* **Disabled items** - Soft or hard disabled states with focus management
* **Auto-close behavior** - Configurable close on selection
* **RTL support** - Right-to-left language navigation

## [Examples](#examples)

### [Menu with trigger](#menu-with-trigger)

Create a dropdown menu by pairing a trigger button with a menu. The trigger opens and closes the menu.

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Mark as read">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >mark_email_read</span
        >
        <span class="label">Mark as read</span>
      </div>

      <div ngMenuItem value="Snooze">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
        <span class="label">Snooze</span>
      </div>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div
        ngMenuItem
        class="menu-item"
        value="Categorize"
        #categorizeItem
        [submenu]="categorizeMenu()"
      >
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >category</span
        >
        <span class="label">Categorize</span>
        <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Mark as important">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label_important</span
              >
              <span class="label">Mark as important</span>
            </div>

            <div ngMenuItem value="Star">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >star</span
              >
              <span class="label">Star</span>
            </div>

            <div ngMenuItem value="Label">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label</span
              >
              <span class="label">Label</span>
            </div>
          </ng-template>
        </div>
      </ng-template>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div ngMenuItem value="Archive">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >archive</span
        >
        <span class="label">Archive</span>
      </div>

      <div ngMenuItem value="Report spam">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
        <span class="label">Report spam</span>
      </div>

      <div ngMenuItem value="Delete">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  border-radius: 0.5rem;
  color: var(--primary-contrast);
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuTrigger]:not([disabled]):hover,
[ngMenuTrigger][aria-expanded='true'],
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div class="group">
        <div ngMenuItem value="Mark as read">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >mark_email_read</span
          >
          <span class="label">Mark as read</span>
        </div>

        <div ngMenuItem value="Snooze">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >snooze</span
          >
          <span class="label">Snooze</span>
        </div>

        <div ngMenuItem value="Delete">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Delete</span>
        </div>
      </div>

      <div class="group">
        <div
          ngMenuItem
          class="menu-item"
          value="Categorize"
          #categorizeItem
          [submenu]="categorizeMenu()"
        >
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >category</span
          >
          <span class="label">Categorize</span>
          <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div class="group">
              <div ngMenuItem value="Mark as important">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >label_important</span
                >
                <span class="label">Mark as important</span>
              </div>

              <div ngMenuItem value="Star">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >star</span
                >
                <span class="label">Star</span>
              </div>

              <div ngMenuItem value="Label">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >label</span
                >
                <span class="label">Label</span>
              </div>
            </div>
          </ng-template>
        </div>
      </ng-template>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  border-radius: 0.5rem;
  border: 1px solid transparent;
  background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
}

[ngMenu] .group {
  padding: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu] .group:first-of-type {
  border-top-left-radius: 1rem;
  border-top-right-radius: 1rem;
}

[ngMenu] .group:last-of-type {
  border-bottom-left-radius: 1rem;
  border-bottom-right-radius: 1rem;
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuTrigger]:hover,
[ngMenuTrigger][aria-expanded='true'] {
  background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()">
  Open Menu
</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Mark as read">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >mark_email_read</span
        >
        <span class="label">Mark as read</span>
      </div>

      <div ngMenuItem value="Snooze">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
        <span class="label">Snooze</span>
      </div>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div
        ngMenuItem
        class="menu-item"
        value="Categorize"
        #categorizeItem
        [submenu]="categorizeMenu()"
      >
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >category</span
        >
        <span class="label">Categorize</span>
        <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Mark as important">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label_important</span
              >
              <span class="label">Mark as important</span>
            </div>

            <div ngMenuItem value="Star">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >star</span
              >
              <span class="label">Star</span>
            </div>

            <div ngMenuItem value="Label">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label</span
              >
              <span class="label">Label</span>
            </div>
          </ng-template>
        </div>
      </ng-template>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div ngMenuItem value="Archive">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >archive</span
        >
        <span class="label">Archive</span>
      </div>

      <div ngMenuItem value="Report spam">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
        <span class="label">Report spam</span>
      </div>

      <div ngMenuItem value="Delete">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  font-family: 'Press Start 2P';
  color: #000;
  background-color: var(--vivid-pink);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngMenuTrigger]:hover {
  transform: translate(1px, 1px);
}

[ngMenuTrigger]:active {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuTrigger]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: 8px;
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

The menu automatically closes when a user selects an item or presses Escape.

### [Context menu](#context-menu)

Context menus appear at the cursor position when users right-click an element.

### app.ts

```
import {Component, viewChild, signal} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  trigger = viewChild.required<MenuTrigger<string>>('trigger');
  contextMenu = viewChild<Menu<string>>('contextMenu');

  // Position of the context menu
  menuPosition = signal({x: 0, y: 0});

  onContextMenu(event: MouseEvent) {
    event.preventDefault();

    // Update position coordinates
    this.menuPosition.set({x: event.clientX, y: event.clientY});

    // Open the menu via the trigger
    this.trigger().open();
  }

  lastAction = signal<string | null>(null);

  onItemSelected(value: string) {
    this.lastAction.set(value);
  }

  onOverlayKeydown(event: KeyboardEvent) {
    if (event.key === 'Escape') {
      this.closeContextMenu();
    }
  }

  closeContextMenu() {
    if (this.trigger().expanded()) {
      this.trigger().close();
    }
  }
}
```

### app.html

```
<!-- Target area for right-click -->
<div (contextmenu)="onContextMenu($event)" class="context-zone">
  Right-click inside this area to open the context menu.
</div>

@if (lastAction()) {
  <p class="last-action">
    <span class="material-symbols-outlined" translate="no" aria-hidden="true">check_circle</span>
    Action performed: <strong>{{ lastAction() }}</strong>
  </p>
}

<!-- Hidden trigger positioned dynamically at cursor coordinates -->
<div
  #triggerEl
  ngMenuTrigger
  #trigger="ngMenuTrigger"
  [menu]="contextMenu()"
  style="position: fixed; visibility: hidden"
  [style.left.px]="menuPosition().x"
  [style.top.px]="menuPosition().y"
></div>

<!-- Overlay container for the menu -->
<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{
    origin: triggerEl,
    usePopover: 'inline',
    hasBackdrop: true,
    backdropClass: 'cdk-overlay-transparent-backdrop',
  }"
  (backdropClick)="closeContextMenu()"
  (overlayKeydown)="onOverlayKeydown($event)"
  cdkAttachPopoverAsChild
>
  <div ngMenu #contextMenu="ngMenu" (itemSelected)="onItemSelected($event)">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Edit">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">edit</span>
        <span class="label">Edit</span>
      </div>

      <div ngMenuItem value="Copy">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >content_copy</span
        >
        <span class="label">Copy</span>
      </div>

      <div role="separator" class="separator"></div>

      <div ngMenuItem value="Delete" style="color: var(--vivid-pink)">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

Position the menu using the `contextmenu` event coordinates.

### [Standalone menu](#standalone-menu)

A standalone menu doesn't require a trigger and remains visible in the interface.

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  hasInteracted = signal(false);
  updateMenu = viewChild<Menu<string>>('updateMenu');
}
```

### app.html

```
<div ngMenu (mouseover)="hasInteracted.set(true)" (focusin)="hasInteracted.set(true)">
  <ng-template ngMenuContent>
    <span id="security-label" class="heading">SECURITY</span>

    <div role="group" aria-labelledby="security-label">
      <div ngMenuItem value="Change password">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >lock_open</span
        >
        <span class="label">Change password</span>
      </div>

      <div ngMenuItem value="Two-factor authentication">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >security_key</span
        >
        <span class="label">Two-factor authentication</span>
      </div>

      <div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >refresh</span
        >
        <span class="label">Reset</span>
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="hasInteracted()"
        [cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu #updateMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Email address">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >email</span
              >
              <span class="label">Email address</span>
            </div>

            <div ngMenuItem value="Phone number">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >phone</span
              >
              <span class="label">Phone number</span>
            </div>

            <div ngMenuItem value="Password">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >vpn_key</span
              >
              <span class="label">Password</span>
            </div>
          </ng-template>
        </div>
      </ng-template>
    </div>

    <div role="separator" class="separator"></div>

    <span id="help-label" class="heading">HELP</span>

    <div role="group" aria-labelledby="help-label">
      <div ngMenuItem value="Support">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">help</span>
        <span class="label">Support</span>
      </div>

      <div ngMenuItem value="Feedback">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >feedback</span
        >
        <span class="label">Feedback</span>
      </div>
    </div>

    <div role="separator" class="separator"></div>

    <div ngMenuItem value="Logout">
      <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">logout</span>
      <span class="label">Logout</span>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenu] {
  margin: 0;
  width: 30rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu] [ngMenu] {
  width: 15rem;
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}

[ngMenu] .heading {
  display: block;
  font-weight: bold;
  opacity: 0.6;
  font-size: 0.75rem;
  padding: 0.75rem;
  letter-spacing: 0.05rem;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  hasInteracted = signal(false);
  updateMenu = viewChild<Menu<string>>('updateMenu');
}
```

### app.html

```
<div
  ngMenu
  class="material-menu"
  (mouseover)="hasInteracted.set(true)"
  (focusin)="hasInteracted.set(true)"
>
  <ng-template ngMenuContent>
    <span id="security-label" class="heading">SECURITY</span>

    <div role="group" aria-labelledby="security-label">
      <div ngMenuItem value="Change password">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >lock_open</span
        >
        <span class="label">Change password</span>
      </div>

      <div ngMenuItem value="Two-factor authentication">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >security_key</span
        >
        <span class="label">Two-factor authentication</span>
      </div>

      <div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >refresh</span
        >
        <span class="label">Reset</span>
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="hasInteracted()"
        [cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu #updateMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Email address">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >email</span
              >
              <span class="label">Email address</span>
            </div>

            <div ngMenuItem value="Phone number">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >phone</span
              >
              <span class="label">Phone number</span>
            </div>

            <div ngMenuItem value="Password">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >vpn_key</span
              >
              <span class="label">Password</span>
            </div>
          </ng-template>
        </div>
      </ng-template>
    </div>

    <div role="separator" class="separator"></div>

    <span id="help-label" class="heading">HELP</span>

    <div role="group" aria-labelledby="help-label">
      <div ngMenuItem value="Support">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">help</span>
        <span class="label">Support</span>
      </div>

      <div ngMenuItem value="Feedback">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >feedback</span
        >
        <span class="label">Feedback</span>
      </div>
    </div>

    <div role="separator" class="separator"></div>

    <div ngMenuItem value="Logout">
      <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">logout</span>
      <span class="label">Logout</span>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngMenu] {
  gap: 3px;
  width: 30rem;
  padding: 1rem;
  border-radius: 1rem;
  display: flex;
  flex-direction: column;
  background-color: color-mix(in srgb, var(--vivid-pink) 5%, var(--page-background));
  border: 1px solid color-mix(in srgb, var(--full-contrast) 10%, var(--page-background));
}

[ngMenu] [ngMenu] {
  width: 15rem;
}

[ngMenu] .group {
  padding: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu] .group:first-of-type {
  border-top-left-radius: 1rem;
  border-top-right-radius: 1rem;
}

[ngMenu] .group:last-of-type {
  border-bottom-left-radius: 1rem;
  border-bottom-right-radius: 1rem;
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}

[ngMenu] .heading {
  display: block;
  font-weight: bold;
  opacity: 0.6;
  font-size: 0.75rem;
  padding: 0.75rem;
  letter-spacing: 0.05rem;
}
```

### app.ts

```
import {Component, signal, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, OverlayModule],
})
export class App {
  hasInteracted = signal(false);
  updateMenu = viewChild<Menu<string>>('updateMenu');
}
```

### app.html

```
<div
  ngMenu
  class="retro-menu"
  (mouseover)="hasInteracted.set(true)"
  (focusin)="hasInteracted.set(true)"
>
  <ng-template ngMenuContent>
    <span id="security-label" class="heading">SECURITY</span>

    <div role="group" aria-labelledby="security-label">
      <div ngMenuItem value="Change password">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >lock_open</span
        >
        <span class="label">Change password</span>
      </div>

      <div ngMenuItem value="Two-factor authentication">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >security_key</span
        >
        <span class="label">Two-factor authentication</span>
      </div>

      <div ngMenuItem value="Reset" #resetItem [submenu]="updateMenu()">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >refresh</span
        >
        <span class="label">Reset</span>
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="hasInteracted()"
        [cdkConnectedOverlay]="{origin: resetItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu #updateMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Email address">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >email</span
              >
              <span class="label">Email address</span>
            </div>

            <div ngMenuItem value="Phone number">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >phone</span
              >
              <span class="label">Phone number</span>
            </div>

            <div ngMenuItem value="Password">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >vpn_key</span
              >
              <span class="label">Password</span>
            </div>
          </ng-template>
        </div>
      </ng-template>
    </div>

    <div role="separator" class="separator"></div>

    <span id="help-label" class="heading">HELP</span>

    <div role="group" aria-labelledby="help-label">
      <div ngMenuItem value="Support">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">help</span>
        <span class="label">Support</span>
      </div>

      <div ngMenuItem value="Feedback">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >feedback</span
        >
        <span class="label">Feedback</span>
      </div>
    </div>

    <div role="separator" class="separator"></div>

    <div ngMenuItem value="Logout">
      <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">logout</span>
      <span class="label">Logout</span>
    </div>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenu] {
  margin-top: 8px;
  width: 30rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu] [ngMenu] {
  width: 15rem;
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}

[ngMenu] .heading {
  display: block;
  font-weight: bold;
  opacity: 0.6;
  font-size: 0.75rem;
  padding: 0.75rem;
  letter-spacing: 0.05rem;
}
```

Standalone menus work well for always-visible action lists or navigation.

### [Disabled menu items](#disabled-menu-items)

Disable specific menu items using the `disabled` input. Control focus behavior with `softDisabled`.

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Mark as read">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >mark_email_read</span
        >
        <span class="label">Mark as read</span>
      </div>

      <div ngMenuItem value="Snooze" [disabled]="true">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
        <span class="label">Snooze</span>
      </div>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div
        ngMenuItem
        class="menu-item"
        value="Categorize"
        #categorizeItem
        [submenu]="categorizeMenu()"
        [disabled]="true"
      >
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >category</span
        >
        <span class="label">Categorize</span>
        <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Mark as important">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label_important</span
              >
              <span class="label">Mark as important</span>
            </div>

            <div ngMenuItem value="Star">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >star</span
              >
              <span class="label">Star</span>
            </div>

            <div ngMenuItem value="Label">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label</span
              >
              <span class="label">Label</span>
            </div>
          </ng-template>
        </div>
      </ng-template>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div ngMenuItem value="Archive">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >archive</span
        >
        <span class="label">Archive</span>
      </div>

      <div ngMenuItem value="Report spam">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
        <span class="label">Report spam</span>
      </div>

      <div ngMenuItem value="Delete">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  border-radius: 0.5rem;
  color: var(--primary-contrast);
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  margin: 0;
  width: 15rem;
  padding: 0.25rem;
  border-radius: 0.5rem;
  border: 1px solid var(--border-color);
  background-color: var(--page-background);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
  border-radius: 0.25rem;
}

[ngMenuItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngMenuTrigger]:not([disabled]):hover,
[ngMenuTrigger][aria-expanded='true'],
[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--border-color);
  margin: 0.25rem 0;
  opacity: 0.25;
}
```

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button ngMenuTrigger #origin #trigger="ngMenuTrigger" [menu]="formatMenu()">Open Menu</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div class="group">
        <div ngMenuItem value="Mark as read">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >mark_email_read</span
          >
          <span class="label">Mark as read</span>
        </div>

        <div ngMenuItem value="Snooze">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >snooze</span
          >
          <span class="label">Snooze</span>
        </div>

        <div ngMenuItem value="Delete" [disabled]="true">
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >delete</span
          >
          <span class="label">Delete</span>
        </div>
      </div>

      <div class="group">
        <div
          ngMenuItem
          class="menu-item"
          value="Categorize"
          #categorizeItem
          [submenu]="categorizeMenu()"
        >
          <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
            >category</span
          >
          <span class="label">Categorize</span>
          <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
            >arrow_right</span
          >
        </div>
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div class="group">
              <div ngMenuItem value="Mark as important">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >label_important</span
                >
                <span class="label">Mark as important</span>
              </div>

              <div ngMenuItem value="Star">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >star</span
                >
                <span class="label">Star</span>
              </div>

              <div ngMenuItem value="Label" [disabled]="true">
                <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                  >label</span
                >
                <span class="label">Label</span>
              </div>
            </div>
          </ng-template>
        </div>
      </ng-template>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  border-radius: 0.5rem;
  border: 1px solid transparent;
  background-color: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  gap: 3px;
  width: 15rem;
  display: flex;
  flex-direction: column;
}

[ngMenu] .group {
  padding: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--page-background);
  box-shadow: 0 1px 2px 1px color-mix(in srgb, var(--primary-contrast) 25%, transparent);
}

[ngMenu] .group:first-of-type {
  border-top-left-radius: 1rem;
  border-top-right-radius: 1rem;
}

[ngMenu] .group:last-of-type {
  border-bottom-left-radius: 1rem;
  border-bottom-right-radius: 1rem;
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  font-size: 0.875rem;
  border-radius: 0.75rem;
}

[ngMenuTrigger]:hover,
[ngMenuTrigger][aria-expanded='true'] {
  background: color-mix(in srgb, var(--vivid-pink) 10%, transparent);
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--primary-contrast));
  background: color-mix(in srgb, var(--vivid-pink) 5%, transparent);
}

[ngMenuItem]:focus,
[ngMenuTrigger]:focus {
  outline: 2px solid var(--vivid-pink);
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 1px solid var(--quaternary-contrast);
  margin: 0.25rem 0;
  opacity: 0.25;
}

[ngMenuItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}
```

### app.ts

```
import {Component, viewChild} from '@angular/core';
import {Menu, MenuContent, MenuItem, MenuTrigger} from '@angular/aria/menu';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Menu, MenuContent, MenuItem, MenuTrigger, OverlayModule],
})
export class App {
  formatMenu = viewChild<Menu<string>>('formatMenu');
  categorizeMenu = viewChild<Menu<string>>('categorizeMenu');
}
```

### app.html

```
<button class="retro-trigger" ngMenuTrigger #trigger="ngMenuTrigger" #origin [menu]="formatMenu()">
  Open Menu
</button>

<ng-template
  [cdkConnectedOverlayOpen]="trigger.expanded()"
  [cdkConnectedOverlay]="{origin, usePopover: 'inline'}"
  [cdkConnectedOverlayPositions]="[
    {originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4},
  ]"
  cdkAttachPopoverAsChild
>
  <div ngMenu class="menu" #formatMenu="ngMenu">
    <ng-template ngMenuContent>
      <div ngMenuItem value="Mark as read" [disabled]="true">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >mark_email_read</span
        >
        <span class="label">Mark as read</span>
      </div>

      <div ngMenuItem value="Snooze">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">snooze</span>
        <span class="label">Snooze</span>
      </div>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div
        ngMenuItem
        class="menu-item"
        value="Categorize"
        #categorizeItem
        [submenu]="categorizeMenu()"
      >
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >category</span
        >
        <span class="label">Categorize</span>
        <span class="icon material-symbols-outlined arrow" translate="no" aria-hidden="true"
          >arrow_right</span
        >
      </div>

      <ng-template
        [cdkConnectedOverlayOpen]="formatMenu.visible()"
        [cdkConnectedOverlay]="{origin: categorizeItem, usePopover: 'inline'}"
        [cdkConnectedOverlayPositions]="[
          {originX: 'end', originY: 'top', overlayY: 'top', overlayX: 'start', offsetX: 6},
        ]"
        cdkAttachPopoverAsChild
      >
        <div ngMenu class="menu" #categorizeMenu="ngMenu">
          <ng-template ngMenuContent>
            <div ngMenuItem value="Mark as important">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label_important</span
              >
              <span class="label">Mark as important</span>
            </div>

            <div ngMenuItem value="Star">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >star</span
              >
              <span class="label">Star</span>
            </div>

            <div ngMenuItem value="Label">
              <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
                >label</span
              >
              <span class="label">Label</span>
            </div>
          </ng-template>
        </div>
      </ng-template>

      <div role="separator" aria-orientation="horizontal" class="separator"></div>

      <div ngMenuItem value="Archive">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true"
          >archive</span
        >
        <span class="label">Archive</span>
      </div>

      <div ngMenuItem value="Report spam">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">report</span>
        <span class="label">Report spam</span>
      </div>

      <div ngMenuItem value="Delete">
        <span class="icon material-symbols-outlined" translate="no" aria-hidden="true">delete</span>
        <span class="label">Delete</span>
      </div>
    </ng-template>
  </div>
</ng-template>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;
  --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background));

  font-family: 'Press Start 2P';

  --retro-button-color: var(--vivid-pink);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngMenuTrigger] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.6rem 2rem;
  font-family: 'Press Start 2P';
  color: #000;
  background-color: var(--vivid-pink);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngMenuTrigger]:hover {
  transform: translate(1px, 1px);
}

[ngMenuTrigger]:active {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, #fff);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngMenuTrigger] .icon {
  font-size: 1.5rem;
  opacity: 0.875;
}

[ngMenu] {
  margin-top: 8px;
  width: 15rem;
  padding: 0.25rem;
  background-color: var(--page-background);
  box-shadow: var(--retro-flat-shadow);
}

[ngMenu][data-visible='false'] {
  display: none;
}

[ngMenuItem] {
  outline: none;
  display: flex;
  cursor: pointer;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-disabled='true']):hover,
[ngMenuItem]:not([aria-disabled='true']):focus,
[ngMenuItem][aria-expanded='true'] {
  background: color-mix(in srgb, var(--border-color) 10%, transparent);
}

[ngMenuTrigger]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: 8px;
}

[ngMenuItem]:focus {
  outline: 4px dashed var(--vivid-pink);
  outline-offset: -4px;
}

[ngMenuItem] .icon {
  opacity: 0.875;
  font-size: 1.25rem;
}

[ngMenuItem] .label {
  flex: 1;
  opacity: 0.875;
  font-size: 0.875rem;
}

[ngMenuItem]:not([aria-expanded='true']) .arrow {
  opacity: 0.5;
}

[ngMenu] .separator {
  border-top: 4px solid #000;
  margin: 0.25rem 0;
  opacity: 0.25;
}

[ngMenuItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}
```

When `[softDisabled]="true"`, disabled items can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation.

## [Testing](#testing)

Angular Aria provides component harnesses for testing menu components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {MenuHarness, MenuItemHarness} from '@angular/aria/menu/testing';
import {MyMenuComponent} from './my-menu'; // Your component

describe('MyMenuComponent', () => {
  let fixture: ComponentFixture<MyMenuComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyMenuComponent],
    });

    fixture = TestBed.createComponent(MyMenuComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should open menu and click item', async () => {
    // Load the menu harness by its trigger text
    const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));

    // Verify initial state
    expect(await menu.isOpen()).toBe(false);

    // Open the menu
    await menu.open();
    expect(await menu.isOpen()).toBe(true);

    // Get items
    const items = await menu.getItems();
    expect(items.length).toBe(3);
    expect(await items[0].getText()).toBe('Item 1');

    // Click first item
    await items[0].click();

    // Menu should close after selection (depending on your implementation)
    expect(await menu.isOpen()).toBe(false);
  });

  it('should interact with submenus', async () => {
    const menu = await loader.getHarness(MenuHarness.with({triggerText: 'Open Menu'}));
    await menu.open();

    // Get the item that triggers a submenu
    const subItem = await loader.getHarness(MenuItemHarness.with({text: 'Submenu'}));
    expect(await subItem.hasSubmenu()).toBe(true);

    // Open submenu
    await subItem.click();
    const submenu = await subItem.getSubmenu();
    expect(submenu).toBeTruthy();
    expect(await submenu!.isOpen()).toBe(true);

    // Interact with submenu items
    const subItems = await submenu!.getItems();
    expect(subItems.length).toBe(1);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Menu`](/api/aria/menu/Menu)
* [`MenuBar`](/api/aria/menu/MenuBar)
* [`MenuItem`](/api/aria/menu/MenuItem)
* [`MenuTrigger`](/api/aria/menu/MenuTrigger)
* [`MenuContent`](/api/aria/menu/MenuContent)

----
url: https://angular.dev/guide/http/setup
----

[`HttpClient`](/api/common/http/HttpClient) is available for injection by default in Angular v21 and later.

## [Providing `HttpClient` through dependency injection](#providing-httpclient-through-dependency-injection)

You can use the [`provideHttpClient`](/api/common/http/provideHttpClient) helper function to configure the default HTTP feature set or add features in the application `providers` in `app.config.ts`.

```
export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(/* add features here, such as withInterceptors(...) */)],
};
```

If your app is using NgModule-based bootstrap instead, you can include [`provideHttpClient`](/api/common/http/provideHttpClient) in the providers of your app's NgModule to configure the default HTTP feature set or add features:

```
@NgModule({
  providers: [provideHttpClient(/* add features here, such as withInterceptors(...) */)],
  // ... other application configuration
})
export class AppModule {}
```

You can then inject the [`HttpClient`](/api/common/http/HttpClient) service as a dependency of your components, services, or other classes:

```
@Service()
export class ConfigService {
  private http = inject(HttpClient);
  // This service can now make HTTP requests via `this.http`.
}
```

## [Configuring features of `HttpClient`](#configuring-features-of-httpclient)

[`provideHttpClient`](/api/common/http/provideHttpClient) accepts a list of optional feature configurations to enable or configure different aspects of the client. This section details the optional features and their usage.

### [`withXhr`](#withxhr)

```
export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withXhr())],
};
```

By default, [`HttpClient`](/api/common/http/HttpClient) uses the [`fetch`](https://developer.mozilla.org/docs/Web/API/Fetch_API) API to make requests. The [`withXhr`](/api/common/http/withXhr) feature switches the client to use the [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest) API instead.

`fetch` is a more modern API and is available in a few environments where `XMLHttpRequest` is not supported. It does have a few limitations, such as not producing upload progress events.

### Do not use [`withXhr`](/api/common/http/withXhr) in server-side rendering (SSR) environments

XHR support on the server is **deprecated** and is intended to be removed in Angular 23. The underlying `xhr2` library does not safely handle redirects: it can forward `Authorization` headers on cross-origin redirects and is susceptible to denial-of-service (DoS) via redirect loops. For SSR applications, use the default `fetch` backend instead.

### [`withInterceptors(...)`](#withinterceptors)

[`withInterceptors`](/api/common/http/withInterceptors) configures the set of interceptor functions which will process requests made through [`HttpClient`](/api/common/http/HttpClient). See the [interceptor guide](guide/http/interceptors) for more information.

### [`withInterceptorsFromDi()`](#withinterceptorsfromdi)

[`withInterceptorsFromDi`](/api/common/http/withInterceptorsFromDi) includes the older style of class-based interceptors in the [`HttpClient`](/api/common/http/HttpClient) configuration. See the [interceptor guide](guide/http/interceptors) for more information.

**HELPFUL:** Functional interceptors (through [`withInterceptors`](/api/common/http/withInterceptors)) have more predictable ordering and we recommend them over DI-based interceptors.

### [`withRequestsMadeViaParent()`](#withrequestsmadeviaparent)

By default, when you configure [`HttpClient`](/api/common/http/HttpClient) using [`provideHttpClient`](/api/common/http/provideHttpClient) within a given injector, this configuration overrides any configuration for [`HttpClient`](/api/common/http/HttpClient) which may be present in the parent injector.

When you add [`withRequestsMadeViaParent()`](/api/common/http/withRequestsMadeViaParent), [`HttpClient`](/api/common/http/HttpClient) is configured to instead pass requests up to the [`HttpClient`](/api/common/http/HttpClient) instance in the parent injector, once they've passed through any configured interceptors at this level. This is useful if you want to *add* interceptors in a child injector while still sending the request through the parent injector's interceptors.

**CRITICAL:** You must configure an instance of [`HttpClient`](/api/common/http/HttpClient) above the current injector, or this option is not valid and you'll get a runtime error when you try to use it.

### [`withJsonpSupport()`](#withjsonpsupport)

### JSONP is deprecated

[`withJsonpSupport`](/api/common/http/withJsonpSupport), [`HttpClientJsonpModule`](/api/common/http/HttpClientJsonpModule), and the `.jsonp()` method on [`HttpClient`](/api/common/http/HttpClient) are deprecated because JSONP can cause cross-site scripting (XSS) vulnerabilities. Use standard HTTP requests with [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) instead.

Including [`withJsonpSupport`](/api/common/http/withJsonpSupport) enables the `.jsonp()` method on [`HttpClient`](/api/common/http/HttpClient), which makes a GET request via the [JSONP convention](https://en.wikipedia.org/wiki/JSONP) for cross-domain loading of data.

**HELPFUL:** Prefer using [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) to make cross-domain requests instead of JSONP when possible.

### [`withXsrfConfiguration(...)`](#withxsrfconfiguration)

Including this option allows customization of [`HttpClient`](/api/common/http/HttpClient)'s built-in XSRF security functionality. See the [security guide](best-practices/security) for more information.

### [`withNoXsrfProtection()`](#withnoxsrfprotection)

Including this option disables [`HttpClient`](/api/common/http/HttpClient)'s built-in XSRF security functionality. See the [security guide](best-practices/security) for more information.

## [`HttpClientModule`-based configuration](#httpclientmodule-based-configuration)

Some applications may configure [`HttpClient`](/api/common/http/HttpClient) using the older API based on NgModules.

This table lists the NgModules available from `@angular/common/http` and how they relate to the provider configuration functions above.

| **NgModule**                                                                      | [`provideHttpClient()`](/api/common/http/provideHttpClient) equivalent |
| --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [`HttpClientModule`](/api/common/http/HttpClientModule)                           | `provideHttpClient(withInterceptorsFromDi(), withXhr())`               |
| [`HttpClientJsonpModule`](/api/common/http/HttpClientJsonpModule) (deprecated)    | [`withJsonpSupport()`](/api/common/http/withJsonpSupport) (deprecated) |
| `HttpClientXsrfModule.withOptions(...)`                                           | `withXsrfConfiguration(...)`                                           |
| [`HttpClientXsrfModule.disable()`](/api/common/http/HttpClientXsrfModule#disable) | [`withNoXsrfProtection()`](/api/common/http/withNoXsrfProtection)      |

**NOTE:** [`HttpClientModule`](/api/common/http/HttpClientModule) and [`HttpClientXsrfModule`](/api/common/http/HttpClientXsrfModule) are also deprecated. Use [`provideHttpClient`](/api/common/http/provideHttpClient) with the corresponding features instead.

### Use caution when using HttpClientModule in multiple injectors

When [`HttpClientModule`](/api/common/http/HttpClientModule) is present in multiple injectors, the behavior of interceptors is poorly defined and depends on the exact options and provider/import ordering.

Prefer [`provideHttpClient`](/api/common/http/provideHttpClient) for multi-injector configurations, as it has more stable behavior. See the [`withRequestsMadeViaParent`](/api/common/http/withRequestsMadeViaParent) feature above.

----
url: https://angular.dev/reference/versions
----

The following tables describe the versions of Node.js, TypeScript, and RxJS that each version of Angular requires.

## [Actively supported versions](#actively-supported-versions)

This table covers [Angular versions under active support](reference/releases#actively-supported-versions).

| Angular                        | Node.js                             | TypeScript     | RxJS               |
| ------------------------------ | ----------------------------------- | -------------- | ------------------ |
| 22.0.x                         | ^22.22.3 \|\| ^24.15.0 \|\| ^26.0.0 | >=6.0.0 <6.1.0 | ^6.5.3 \|\| ^7.4.0 |
| 21.0.x \|\| 21.1.x \|\| 21.2.x | ^20.19.0 \|\| ^22.12.0 \|\| ^24.0.0 | >=5.9.0 <6.0.0 | ^6.5.3 \|\| ^7.4.0 |
| 20.2.x \|\| 20.3.x             | ^20.19.0 \|\| ^22.12.0 \|\| ^24.0.0 | >=5.8.0 <6.0.0 | ^6.5.3 \|\| ^7.4.0 |
| 20.0.x \|\| 20.1.x             | ^20.19.0 \|\| ^22.12.0 \|\| ^24.0.0 | >=5.8.0 <5.9.0 | ^6.5.3 \|\| ^7.4.0 |

## [Unsupported Angular versions](#unsupported-angular-versions)

This table covers Angular versions that are no longer under long-term support (LTS). This information was correct when each version went out of LTS and is provided without any further guarantees. It is listed here for historical reference.

| Angular            | Node.js                              | TypeScript     | RxJS               |
| ------------------ | ------------------------------------ | -------------- | ------------------ |
| 19.2.x             | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0  | >=5.5.0 <5.9.0 | ^6.5.3 \|\| ^7.4.0 |
| 19.1.x             | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0  | >=5.5.0 <5.8.0 | ^6.5.3 \|\| ^7.4.0 |
| 19.0.x             | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0  | >=5.5.0 <5.7.0 | ^6.5.3 \|\| ^7.4.0 |
| 18.1.x \|\| 18.2.x | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0  | >=5.4.0 <5.6.0 | ^6.5.3 \|\| ^7.4.0 |
| 18.0.x             | ^18.19.1 \|\| ^20.11.1 \|\| ^22.0.0  | >=5.4.0 <5.5.0 | ^6.5.3 \|\| ^7.4.0 |
| 17.3.x             | ^18.13.0 \|\| ^20.9.0                | >=5.2.0 <5.5.0 | ^6.5.3 \|\| ^7.4.0 |
| 17.1.x \|\| 17.2.x | ^18.13.0 \|\| ^20.9.0                | >=5.2.0 <5.4.0 | ^6.5.3 \|\| ^7.4.0 |
| 17.0.x             | ^18.13.0 \|\| ^20.9.0                | >=5.2.0 <5.3.0 | ^6.5.3 \|\| ^7.4.0 |
| 16.1.x \|\| 16.2.x | ^16.14.0 \|\| ^18.10.0               | >=4.9.3 <5.2.0 | ^6.5.3 \|\| ^7.4.0 |
| 16.0.x             | ^16.14.0 \|\| ^18.10.0               | >=4.9.3 <5.1.0 | ^6.5.3 \|\| ^7.4.0 |
| 15.1.x \|\| 15.2.x | ^14.20.0 \|\| ^16.13.0 \|\| ^18.10.0 | >=4.8.2 <5.0.0 | ^6.5.3 \|\| ^7.4.0 |
| 15.0.x             | ^14.20.0 \|\| ^16.13.0 \|\| ^18.10.0 | \~4.8.2        | ^6.5.3 \|\| ^7.4.0 |
| 14.2.x \|\| 14.3.x | ^14.15.0 \|\| ^16.10.0               | >=4.6.2 <4.9.0 | ^6.5.3 \|\| ^7.4.0 |
| 14.0.x \|\| 14.1.x | ^14.15.0 \|\| ^16.10.0               | >=4.6.2 <4.8.0 | ^6.5.3 \|\| ^7.4.0 |
| 13.3.x \|\| 13.4.x | ^12.20.0 \|\| ^14.15.0 \|\| ^16.10.0 | >=4.4.3 <4.7.0 | ^6.5.3 \|\| ^7.4.0 |
| 13.1.x \|\| 13.2.x | ^12.20.0 \|\| ^14.15.0 \|\| ^16.10.0 | >=4.4.3 <4.6.0 | ^6.5.3 \|\| ^7.4.0 |
| 13.0.x             | ^12.20.0 \|\| ^14.15.0 \|\| ^16.10.0 | \~4.4.3        | ^6.5.3 \|\| ^7.4.0 |
| 12.2.x             | ^12.14.0 \|\| ^14.15.0               | >=4.2.3 <4.4.0 | ^6.5.3 \|\| ^7.0.0 |
| 12.1.x             | ^12.14.0 \|\| ^14.15.0               | >=4.2.3 <4.4.0 | ^6.5.3             |
| 12.0.x             | ^12.14.0 \|\| ^14.15.0               | \~4.2.3        | ^6.5.3             |
| 11.2.x             | ^10.13.0 \|\| ^12.11.0               | >=4.0.0 <4.2.0 | ^6.5.3             |
| 11.1.x             | ^10.13.0 \|\| ^12.11.0               | >=4.0.0 <4.2.0 | ^6.5.3             |
| 11.0.x             | ^10.13.0 \|\| ^12.11.0               | \~4.0.0        | ^6.5.3             |
| 10.2.x             | ^10.13.0 \|\| ^12.11.0               | >=3.9.0 <4.1.0 | ^6.5.3             |
| 10.1.x             | ^10.13.0 \|\| ^12.11.0               | >=3.9.0 <4.1.0 | ^6.5.3             |
| 10.0.x             | ^10.13.0 \|\| ^12.11.0               | \~3.9.0        | ^6.5.3             |
| 9.1.x              | ^10.13.0 \|\| ^12.11.0               | >=3.6.0 <3.9.0 | ^6.5.3             |
| 9.0.x              | ^10.13.0 \|\| ^12.11.0               | >=3.6.0 <3.8.0 | ^6.5.3             |

### [Before v9](#before-v9)

Until Angular v9, Angular and Angular CLI versions were not synced.

| Angular                     | Angular CLI                 | Node.js             | TypeScript     | RxJS   |
| --------------------------- | --------------------------- | ------------------- | -------------- | ------ |
| 8.2.x                       | 8.2.x \|\| 8.3.x            | ^10.9.0             | >=3.4.2 <3.6.0 | ^6.4.0 |
| 8.0.x \|\| 8.1.x            | 8.0.x \|\| 8.1.x            | ^10.9.0             | \~3.4.2        | ^6.4.0 |
| 7.2.x                       | 7.2.x \|\| 7.3.x            | ^8.9.0 \|\| ^10.9.0 | >=3.1.3 <3.3.0 | ^6.0.0 |
| 7.0.x \|\| 7.1.x            | 7.0.x \|\| 7.1.x            | ^8.9.0 \|\| ^10.9.0 | \~3.1.3        | ^6.0.0 |
| 6.1.x                       | 6.1.x \|\| 6.2.x            | ^8.9.0              | >=2.7.2 <3.0.0 | ^6.0.0 |
| 6.0.x                       | 6.0.x                       | ^8.9.0              | \~2.7.2        | ^6.0.0 |
| 5.2.x                       | 1.6.x \|\| 1.7.x            | ^6.9.0 \|\| ^8.9.0  | >=2.4.2 <2.7.0 | ^5.5.0 |
| 5.0.x \|\| 5.1.x            | 1.5.x                       | ^6.9.0 \|\| ^8.9.0  | \~2.4.2        | ^5.5.0 |
| 4.2.x \|\| 4.3.x \|\| 4.4.x | 1.4.x                       | ^6.9.0 \|\| ^8.9.0  | >=2.1.6 <2.5.0 | ^5.0.1 |
| 4.2.x \|\| 4.3.x \|\| 4.4.x | 1.3.x                       | ^6.9.0              | >=2.1.6 <2.5.0 | ^5.0.1 |
| 4.0.x \|\| 4.1.x            | 1.0.x \|\| 1.1.x \|\| 1.2.x | ^6.9.0              | >=2.1.6 <2.4.0 | ^5.0.1 |
| 2.x                         | -                           | ^6.9.0              | >=1.8.0 <2.2.0 | ^5.0.1 |

## [Browser support](#browser-support)

Angular uses the ["widely available" Baseline](https://web.dev/baseline) to define browser support. For each major version, Angular supports browsers included in the Baseline of a chosen date near the release date for that major.

The "widely available" Baseline includes browsers released less than 30 months (2.5 years) of the chosen date within Baseline's core browser set (Chrome, Edge, Firefox, Safari) and targets supporting approximately 95% of web users.

| Angular | Baseline Date | Browser Set                                                                                                                    |
| ------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| v22     | 2026-05-07    | [Browser Set](https://web-platform-dx.github.io/supported-browsers/?widelyAvailableOnDate=2026-05-07\&includeDownstream=false) |
| v21     | 2025-10-20    | [Browser Set](https://web-platform-dx.github.io/supported-browsers/?widelyAvailableOnDate=2025-10-20\&includeDownstream=false) |
| v20     | 2025-04-30    | [Browser Set](https://web-platform-dx.github.io/supported-browsers/?widelyAvailableOnDate=2025-04-30\&includeDownstream=false) |

Angular versions prior to v20 support the following specific browser versions:

| Browser | Supported versions                        |
| :------ | :---------------------------------------- |
| Chrome  | 2 most recent versions                    |
| Firefox | latest and extended support release (ESR) |
| Edge    | 2 most recent major versions              |
| Safari  | 2 most recent major versions              |
| iOS     | 2 most recent major versions              |
| Android | 2 most recent major versions              |

## [Polyfills](#polyfills)

Angular is built on the latest standards of the web platform. Targeting such a wide range of browsers is challenging because they do not support all features of modern browsers. You compensate by loading polyfill scripts ("polyfills") for the browsers that you must support. See instructions on how to include polyfills into your project below.

**IMPORTANT:** The suggested polyfills are the ones that run full Angular applications. You might need additional polyfills to support features not covered by this list.

**HELPFUL:** Polyfills cannot magically transform an old, slow browser into a modern, fast one.

## [Enabling polyfills with CLI projects](#enabling-polyfills-with-cli-projects)

The [Angular CLI](tools/cli) provides support for polyfills. If you are not using the CLI to create your projects, see [Polyfill instructions for non-CLI users](#polyfills-for-non-cli-users).

The `polyfills` options of the [browser and test builder](tools/cli/cli-builder) can be a full path for a file (Example: `src/polyfills.ts`) or, relative to the current workspace or module specifier (Example: `zone.js`).

If you create a TypeScript file, make sure to include it in the `files` property of your `tsconfig` file.

```
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    ...
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ]
  ...
}
```

## [Polyfills for non-CLI users](#polyfills-for-non-cli-users)

If you are not using the CLI, add your polyfill scripts directly to the host web page (`index.html`).

For example:

### src/index.html

```
<!-- pre-zone polyfills -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script>
  /**
   * you can configure some zone flags which can disable zone interception for some
   * asynchronous activities to improve startup performance - use these options only
   * if you know what you are doing as it could result in hard to trace down bugs.
   */
  // __Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
  // __Zone_disable_on_property = true; // disable patch onProperty such as onclick
  // __zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
  /*
   * in Edge developer tools, the addEventListener will also be wrapped by zone.js
   * with the following flag, it will bypass `zone.js` patch for Edge.
   */
  // __Zone_enable_cross_context_check = true;
</script>
<!-- zone.js required by Angular -->
<script src="node_modules/zone.js/bundles/zone.umd.js"></script>
<!-- application polyfills -->
```

----
url: https://angular.dev/api/common/LOCATION_INITIALIZED
----

```
    
      const LOCATION_INITIALIZED: InjectionToken<Promise<any>>;
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/handleAutoChangeDetectionStatus
----

```
function handleAutoChangeDetectionStatus(
  handler: (status: AutoChangeDetectionStatus) => void,
): void;
```

@paramhandler`(status: AutoChangeDetectionStatus) => void`

The handler for the auto change detection status.

@returns`void`

Jump to details

----
url: https://angular.dev/api/platform-browser/enableDebugTools
----

```
function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T>;
```

@paramref`ComponentRef<T>`

@returns`ComponentRef<T>`

## [Description](#description)

Enabled Angular debug tools that are accessible via your browser's developer console.

Usage:

1. Open developer console (e.g. in Chrome Ctrl + Shift + j)
2. Type `ng.` (usually the console will show auto-complete suggestion)
3. Try the change detection profiler `ng.profiler.timeChangeDetection()` then hit Enter.

Jump to details

----
url: https://angular.dev/api/router/mapToCanMatch
----

```
function mapToCanMatch(providers: Type<CanMatch>[]): CanMatchFn[];
```

@paramproviders`Type<CanMatch>[]`

@returns`CanMatchFn[]`

## [Description](#description)

Maps an array of injectable classes with canMatch functions to an array of equivalent [`CanMatchFn`](/api/router/CanMatchFn) for use in a [`Route`](/api/router/Route) definition.

Usage

```
@Injectable({providedIn: 'root'})
export class AdminGuard {
  canActivate() {
    return true;
  }
}

const route: Route = {
  path: 'admin',
  canActivate: mapToCanActivate([AdminGuard]),
};
```

Jump to details

----
url: https://angular.dev/api/router/RouterLink
----

```
    
      class RouterLink implements OnChanges ,OnDestroy {}
    
    
```

## [Description](#description)

When applied to an element in a template, makes that element a link that initiates navigation to a route. Navigation opens one or more routed components in one or more `<router-outlet>` locations on the page.

Given a route configuration `[{ path: 'user/:name', component: UserCmp }]`, the following creates a static link to the route: `<a routerLink="/user/bob">link to user component</a>`

You can use dynamic values to generate the link. For a dynamic link, pass an array of path segments, followed by the params for each segment. For example, `['/team', teamId, 'user', userName, {details: true}]` generates a link to `/team/11/user/bob;details=true`.

Multiple static segments can be merged into one term and combined with dynamic segments. For example, `['/team/11/user', userName, {details: true}]`

The input that you provide to the link is treated as a delta to the current URL. For instance, suppose the current URL is `/user/(box//aux:team)`. The link `<a [routerLink]="['/user/jim']">Jim</a>` creates the URL `/user/(jim//aux:team)`. See [`Router#createUrlTree`](/api/router/Router#createUrlTree) for more information.

## [Exported by](#exported-by)

* `RouterModule`

## [Usage Notes](#usage-notes)

You can use absolute or relative paths in a link, set query parameters, control how parameters are handled, and keep a history of navigation states.

### [Relative link paths](#relative-link-paths)

The first segment name can be prepended with `/`, `./`, or `../`.

* If the first segment begins with `/`, the router looks up the route from the root of the app.
* If the first segment begins with `./`, or doesn't begin with a slash, the router looks in the children of the current activated route.
* If the first segment begins with `../`, the router goes up one level in the route tree.

### [Setting and handling query params and fragments](#setting-and-handling-query-params-and-fragments)

The following link adds a query parameter and a fragment to the generated URL:

```
<a [routerLink]="['/user/bob']" [queryParams]="{debug: true}"
fragment="education"> link to user component
</a>
```

By default, the directive constructs the new URL using the given query parameters. The example generates the link: `/user/bob?debug=true#education`.

You can instruct the directive to handle query parameters differently by specifying the `queryParamsHandling` option in the link. Allowed values are:

* `'merge'`: Merge the given `queryParams` into the current query params.
* `'preserve'`: Preserve the current query params.

For example:

```
<a [routerLink]="['/user/bob']" [queryParams]="{debug: true}"
queryParamsHandling="merge"> link to user component
</a>
```

`queryParams`, `fragment`, `queryParamsHandling`, `preserveFragment`, and `relativeTo` cannot be used when the `routerLink` input is a [`UrlTree`](/api/router/UrlTree).

See [`UrlCreationOptions#queryParamsHandling`](/api/router/UrlCreationOptions#queryParamsHandling).

### [Preserving navigation history](#preserving-navigation-history)

You can provide a `state` value to be persisted to the browser's [`History.state` property](https://developer.mozilla.org/en-US/docs/Web/API/History#Properties). For example:

```
<a [routerLink]="['/user/bob']" [state]="{tracingId: 123}">
  link to user component
</a>
```

Use [`Router#currentNavigation`](/api/router/Router#currentNavigation) to retrieve a saved Signal navigation-state value. For example, to capture the `tracingId` during the [`NavigationStart`](/api/router/NavigationStart) event:

```
// Get NavigationStart events
router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => {
  const navigation = router.currentNavigation();
  tracingService.trace({id: navigation.extras.state.tracingId});
});
```

### [RouterLink compatible custom elements](#routerlink-compatible-custom-elements)

In order to make a custom element work with routerLink, the corresponding custom element must implement the `href` attribute and must list `href` in the array of the static property/getter `observedAttributes`.

Jump to details

----
url: https://angular.dev/api/router/withDisabledInitialNavigation
----

## [API](/api/router/withDisabledInitialNavigation#api)

```
function withDisabledInitialNavigation(): DisabledInitialNavigationFeature;
```

@returns`DisabledInitialNavigationFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Description](/api/router/withDisabledInitialNavigation#description)

Disables initial navigation.

Use if there is a reason to have more control over when the router starts its initial navigation due to some complex initialization logic.

## [Usage Notes](/api/router/withDisabledInitialNavigation#usage-notes)

Basic example of how you can disable initial navigation:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withDisabledInitialNavigation())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/api/common/NgClass
----

```
    
      class NgClass implements DoCheck {}
    
    
```

@param\_ngEl`ElementRef<any>`

@param\_renderer`Renderer2`

@returns`NgClass`

@returns`void`

## [Description](#description)

Adds and removes CSS classes on an HTML element.

The CSS classes are updated as follows, depending on the type of the expression evaluation:

* `string` - the CSS classes listed in the string (space delimited) are added,
* `Array` - the CSS classes declared as Array elements are added,
* `Object` - keys are CSS classes that get added when the expression given in the value evaluates to a truthy value, otherwise they are removed.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

```
<some-element [ngClass]="stringExp|arrayExp|objExp|Set">...</some-element>

<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
```

For more simple use cases you can use the [class bindings](/guide/templates/binding#css-class-and-style-property-bindings) directly. It doesn't require importing a directive.

```
<some-element [class]="'first second'">...</some-element>

<some-element [class.expanded]="isExpanded">...</some-element>

<some-element [class]="['first', 'second']">...</some-element>

<some-element [class]="{'first': true, 'second': true, 'third': false}">...</some-element>
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/DragAxis
----

```
    
      type DragAxis = 'x' | 'y'
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/minDate
----

```
function minDate<
  TValue extends Date | null,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  minDateValue: Date | LogicFn<TValue, Date | undefined, TPathKind>,
  config?: BaseValidatorConfig<TValue, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@paramminDateValue`Date | LogicFn<TValue, Date | undefined, TPathKind>`

The minimum date, or a LogicFn that returns the minimum date.

@paramconfig`BaseValidatorConfig<TValue, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`minDateError()`](/api/forms/signals/minDateError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

## [Description](#description)

Binds a validator to the given path that requires the value to be greater than or equal to the given [`minDate`](/api/forms/signals/minDate). This function can only be called on date paths. In addition to binding a validator, this function adds [`MIN`](/api/forms/signals/MIN) property to the field.

When the field is bound to an `<input type="date">`, its value is a `Date` at midnight UTC, so the minimum should be built the same way, e.g. `new Date('2026-06-01')` or `new Date(Date.UTC(2026, 5, 1))`. A `Date` created from local time, such as `new Date(2026, 5, 1)` or `new Date()`, is offset by the user's time zone, which can make the limit date itself invalid.

Jump to details

----
url: https://angular.dev/errors/NG0904
----

Angular throws this error, with the message `unsafe value used in a resource URL context`, when a template or host binding sets an attribute that loads an external resource — like `<iframe src>` or `<link href>` — to a value that hasn't been explicitly marked as trusted.

Resource URLs are treated differently from regular URLs. Angular can sanitize a plain URL, but it can't make an arbitrary resource URL safe because these attributes cause the browser to fetch and execute external content. So Angular rejects untrusted values rather than trying to sanitize them.

The attributes that trigger this check are:

| Element    | Attribute(s)       |
| ---------- | ------------------ |
| `<base>`   | `href`             |
| `<embed>`  | `src`              |
| `<frame>`  | `src`              |
| `<iframe>` | `src`              |
| `<link>`   | `href`             |
| `<object>` | `codebase`, `data` |

## [Debugging the error](#debugging-the-error)

The error is thrown when any plain string is bound to one of the attributes above, even a static-looking URL:

```
@Component({
  selector: 'app-root',
  template: `<iframe [src]="url"></iframe>`,
})
export class App {
  url = 'https://example.com/embed';
}
```

Passing the value through [`DomSanitizer.sanitize()`](/api/platform-browser/DomSanitizer#sanitize) with [`SecurityContext.URL`](/api/core/SecurityContext#URL) does not help. `sanitize()` returns a plain string, and a plain string is never accepted in a resource URL context:

```
@Component({
  selector: 'app-root',
  template: `<iframe [src]="sanitized"></iframe>`,
})
export class App {
  private readonly sanitizer = inject(DomSanitizer);
  sanitized = this.sanitizer.sanitize(SecurityContext.URL, 'https://example.com/embed'); // still throws NG0904
}
```

Calling `sanitize()` with [`SecurityContext.RESOURCE_URL`](/api/core/SecurityContext#RESOURCE_URL) instead throws too, but with [NG05201](errors/NG05201), because `sanitize()` cannot make an arbitrary string safe in a resource URL context.

If the URL comes from a trusted, controlled source, mark it safe using [`DomSanitizer.bypassSecurityTrustResourceUrl`](/api/platform-browser/DomSanitizer#bypassSecurityTrustResourceUrl):

```
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  template: `<iframe [src]="safeUrl"></iframe>`,
})
export class App {
  safeUrl: SafeResourceUrl = inject(DomSanitizer).bypassSecurityTrustResourceUrl(
    'https://example.com/embed',
  );
}
```

**CRITICAL:** `bypassSecurityTrustResourceUrl` is an escape hatch, not a sanitizer. Never pass user-supplied URLs through it — that would allow an attacker to load arbitrary content, including malicious scripts. Only use it when the value is fully under your control.

For more information, see the [Security guide](best-practices/security#trusting-safe-values).

----
url: https://angular.dev/reference/releases
----

We recognize that you need stability from the Angular framework. Stability ensures that reusable components and libraries, tutorials, tools, and learned practices don't become obsolete unexpectedly. Stability is essential for the ecosystem around Angular to thrive.

We also share with you the need for Angular to keep evolving. We strive to ensure that the foundation on top of which you are building is continuously improving and enabling you to stay up-to-date with the rest of the web ecosystem and your user needs.

This document contains the practices that we follow to provide you with a leading-edge application development platform, balanced with stability. We strive to ensure that future changes are always introduced in a predictable way. We want everyone who depends on Angular to know when and how new features are added, and to be well-prepared when obsolete ones are removed.

Sometimes *breaking changes*, such as the removal of APIs or features, are necessary to innovate and stay current with evolving best practices, changing dependencies, or shifts in the web platform. These breaking changes go through a deprecation process explained in our [deprecation policy](#deprecation-policy).

To make these transitions as straightforward as possible, the Angular team makes these commitments:

* We work hard to minimize the number of breaking changes and to provide migration tools when possible
* We follow the deprecation policy described here, so you have time to update your applications to the latest APIs and best practices

**HELPFUL:** The practices described in this document apply to Angular 2.0 and later. If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade "Upgrading from AngularJS"). *AngularJS* is the name for all v1.x versions of Angular.

## [Angular versioning](#angular-versioning)

Angular version numbers indicate the level of changes that are introduced by the release. This use of [semantic versioning](https://semver.org/ "Semantic Versioning Specification") helps you understand the potential impact of updating to a new version.

Angular version numbers have three parts: `major.minor.patch`. For example, version 7.2.11 indicates major version 7, minor version 2, and patch level 11.

The version number is incremented based on the level of change included in the release.

| Level of change | Details                                                                                                                                                                                                                                                                                                                                                                                                                           |
| :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Major release   | Contains significant new features, some but minimal developer assistance is expected during the update. When updating to a new major release, you might need to run update scripts, refactor code, run additional tests, and learn new APIs.                                                                                                                                                                                      |
| Minor release   | Contains new smaller features. Minor releases are fully backward-compatible; no developer assistance is expected during update, but you can optionally modify your applications and libraries to begin using new APIs, features, and capabilities that were added in the release. We update peer dependencies in minor versions by expanding the supported versions, but we do not require projects to update these dependencies. |
| Patch release   | Low risk, bug fix release. No developer assistance is expected during update.                                                                                                                                                                                                                                                                                                                                                     |

**HELPFUL:** As of Angular version 7, the major versions of Angular core and the CLI are aligned. This means that in order to use the CLI as you develop an Angular app, the version of `@angular/core` and the CLI need to be the same.

### [Preview releases](#preview-releases)

We let you preview what's coming by providing "Next" and Release Candidates (`rc`) pre-releases for each major and minor release:

| Pre-release type  | Details                                                                                                                                                                      |
| :---------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Next              | The release that is under active development and testing. The next release is indicated by a release tag appended with the `-next` identifier, such as `8.1.0-next.0`.       |
| Release candidate | A release that is feature complete and in final testing. A release candidate is indicated by a release tag appended with the `-rc` identifier, such as version `8.1.0-rc.0`. |

The latest `next` or `rc` pre-release version of the documentation is available at [next.angular.dev](https://next.angular.dev).

## [Release frequency](#release-frequency)

We work toward a regular schedule of releases, so that you can plan and coordinate your updates with the continuing evolution of Angular.

**HELPFUL:** Dates are offered as general guidance and are subject to change.

In general, expect the following release cycle:

* A major release every 12 months
* 4-6 minor releases for each major release
* A patch release and pre-release (`next` or `rc`) build almost every week

This cadence of releases gives eager developers access to new features as soon as they are fully developed and pass through our code review and integration testing processes, while maintaining the stability and reliability of the platform for production users that prefer to receive features after they have been validated by Google and other developers that use the pre-release builds.

**HELPFUL:** Until Angular v22, Angular had a 6-month major release cycle, with 1-3 minor releases for each major release

## [Support policy and schedule](#support-policy-and-schedule)

**HELPFUL:** Approximate dates are offered as general guidance and are subject to change.

### [Release schedule](#release-schedule)

| Version | Date               |
| :------ | :----------------- |
| v22.1   | Week of 2026-07-27 |
| v22.2   | Week of 2026-09-21 |
| v22.3   | \~ November 2026   |
| v22.4   | \~ January 2027    |
| v22.5   | \~ March 2027      |
| v23.0   | \~ June 2027       |

### [Support window](#support-window)

All major releases are typically supported for 24 months.

| Support stage   | Support Timing | Details                                                             |
| :-------------- | :------------- | :------------------------------------------------------------------ |
| Active          | 12 months      | Regularly-scheduled updates and patches are released                |
| Long-term (LTS) | 12 months      | Only [critical fixes and security patches](#lts-fixes) are released |

### [Actively supported versions](#actively-supported-versions)

The following table provides the status for Angular versions under support.

| Version | Status | Released   | Active ends | LTS ends   |
| :------ | :----- | :--------- | :---------- | :--------- |
| ^22.0.0 | Active | 2026-06-03 | 2027-06     | 2028-06    |
| ^21.0.0 | LTS    | 2025-11-19 | 2026-06-03  | 2027-06    |
| ^20.0.0 | LTS    | 2025-05-28 | 2025-11-19  | 2026-11-28 |

**IMPORTANT:** Angular versions v2 to v19 are no longer supported.

### [LTS fixes](#lts-fixes)

As a general rule, a fix is considered for an LTS version if it resolves one of:

* A newly identified security vulnerability,
* A regression, since the start of LTS, caused by a 3rd party change, such as a new browser version.

## [Deprecation policy](#deprecation-policy)

When the Angular team intends to remove an API or feature, it will be marked as *deprecated*. This occurs when an API is obsolete, superseded by another API, or otherwise discontinued. Deprecated API remain available through their deprecated phase, which lasts a minimum one major version (approximately one year).

To help ensure that you have sufficient time and a clear path to update, this is our deprecation policy:

| Deprecation stages | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Announcement       | We announce deprecated APIs and features in the [change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log"). Deprecated APIs appear in the [documentation](api?status=8) with ~~strikethrough~~. When we announce a deprecation, we also announce a recommended update path. Additionally, all deprecated APIs are annotated with `@deprecated` in the corresponding documentation, which enables text editors and IDEs to provide hints if your project depends on them. |
| Deprecation period | When an API or a feature is deprecated, it is still present in at least the next major release (period of at least 12 months). After that, deprecated APIs and features are candidates for removal. A deprecation can be announced in any release, but the removal of a deprecated API or feature happens only in major release. Until a deprecated API or feature is removed, it is maintained according to the LTS support policy, meaning that only critical and security issues are fixed.             |
| npm dependencies   | We only make npm dependency updates that require changes to your applications in a major release. In minor releases, we update peer dependencies by expanding the supported versions, but we do not require projects to update these dependencies until a future major version. This means that during minor Angular releases, npm dependency updates within Angular applications and libraries are optional.                                                                                              |

## [Compatibility policy](#compatibility-policy)

Angular is a collection of many packages, subprojects, and tools. To prevent accidental use of private APIs and so that you can clearly understand what is covered by the practices described here — we document what is and is not considered our public API surface. For details, see [Supported Public API Surface of Angular](https://github.com/angular/angular/blob/main/contributing-docs/public-api-surface.md "Supported Public API Surface of Angular").

To guarantee backward compatibility of Angular we run a series of checks before we merge any change:

* Unit tests and integration tests
* Comparing the type definitions of the public API surface before and after the change
* Running the tests of all the applications at Google that depend on Angular

Any changes to the public API surface are made in accordance with the versioning, support, and depreciation policies previously described. In exceptional cases, such as critical security patches, fixes may introduce backwards incompatible changes. Such exceptional cases are accompanied by explicit notice on the framework's official communication channels.

## [Breaking change policy and update paths](#breaking-change-policy-and-update-paths)

Breaking change requires you to do work because the state after it is not backward compatible with the state before it. You can find the rare exceptions from this rule in the [Compatibility policy](#compatibility-policy). Examples of breaking changes are the removal of public APIs or other changes of the type definition of Angular, changing the timing of calls, or updating to a new version of a dependency of Angular, which includes breaking changes itself.

To support you in case of breaking changes in Angular:

* We follow our [deprecation policy](#deprecation-policy) before we remove a public API
* Support update automation via the `ng update` command. It provides code transformations which we often have tested ahead of time over hundreds of thousands of projects at Google
* Step by step instructions how to update from one major version to another at the ["Angular Update Guide"](update-guide)

You can `ng update` to any version of Angular, provided that the following criteria are met:

* The version you want to update *to* is supported.
* The version you want to update *from* is within one major version of the version you want to upgrade to.

For example, you can update from version 11 to version 12, provided that version 12 is still supported. If you want to update across multiple major versions, perform each update one major version at a time. For example, to update from version 10 to version 12:

1. Update from version 10 to version 11.
2. Update from version 11 to version 12.

## [Developer Preview](#developer-preview)

Occasionally we introduce new APIs under the label of "Developer Preview". These are APIs that are fully functional and polished, but that we are not ready to stabilize under our normal deprecation policy.

This may be because we want to gather feedback from real applications before stabilization, or because the associated documentation or migration tooling is not fully complete. Feedback can be provided via a [GitHub issue](https://github.com/angular/angular/issues), where developers can share their experiences, report bugs, or suggest improvements to help refine the feature.

The policies and practices that are described in this document do not apply to APIs marked as Developer Preview. Such APIs can change at any time, even in new patch versions of the framework. Teams should decide for themselves whether the benefits of using Developer Preview APIs are worth the risk of breaking changes outside of our normal use of semantic versioning.

## [Experimental](#experimental)

These APIs might not become stable at all or have significant changes before becoming stable.

The policies and practices that are described in this document do not apply to APIs marked as experimental. Such APIs can change at any time, even in new patch versions of the framework. Teams should decide for themselves whether the benefits of using experimental APIs are worth the risk of breaking changes outside of our normal use of semantic versioning.

----
url: https://angular.dev/api/core/INJECTOR
----

```
    
      const INJECTOR: InjectionToken<Injector>;
    
    
```

## [Description](#description)

An InjectionToken that gets the current [`Injector`](/api/core/Injector) for `createInjector()`-style injectors.

Requesting this token instead of [`Injector`](/api/core/Injector) allows `StaticInjector` to be tree-shaken from a project.

Jump to details

----
url: https://angular.dev/api/router/TitleStrategy
----

```
    
      abstract class TitleStrategy {}
    
    
```

Performs the application title update.

@paramsnapshot`RouterStateSnapshot`

## [Description](#description)

Provides a strategy for setting the page title after a router navigation.

The built-in implementation traverses the router state snapshot and finds the deepest primary outlet with `title` property. Given the [`Routes`](/api/router/Routes) below, navigating to `/base/child(popup:aux)` would result in the document title being set to "child".

```
[
  {path: 'base', title: 'base', children: [
    {path: 'child', title: 'child'},
  ],
  {path: 'aux', outlet: 'popup', title: 'popupTitle'}
]
```

This class can be used as a base class for custom title strategies. That is, you can create your own class that extends the [`TitleStrategy`](/api/router/TitleStrategy). Note that in the above example, the `title` from the named outlet is never used. However, a custom strategy might be implemented to incorporate titles in named outlets.

Jump to details

----
url: https://angular.dev/api/platform-server/ServerModule
----

```
    
      class ServerModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/testing/BrowserTestingModule
----

```
    
      class BrowserTestingModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragMove
----

```
    
      interface CdkDragMove<T = any> {}
    
    
```

Item that is being dragged.

Position of the user's pointer on the page.

Native event that is causing the dragging.

Distance in pixels that the user has dragged since the drag sequence started.

Indicates the direction in which the user is dragging the element along each axis. `1` means that the position is increasing (e.g. the user is moving to the right or downwards), whereas `-1` means that it's decreasing (they're moving to the left or upwards). `0` means that the position hasn't changed.

Jump to details

----
url: https://angular.dev/api/forms/isFormGroup
----

```
    
      const isFormGroup: (control: unknown) => control is FormGroup<any>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/providePlatformInitializer
----

```
function providePlatformInitializer(initializerFn: () => void): StaticProvider;
```

@paraminitializerFn`() => void`

@returns`StaticProvider`

## [Description](#description)

This function is used to provide initialization functions that will be executed upon initialization of the platform injector.

Note that the provided initializer is run in the injection context.

## [Usage Notes](#usage-notes)

The platform initializer should be provided during platform creation:

```
const platformRef = platformBrowser([ providePlatformInitializer(() =>  ...) ]);

bootstrapApplication(App, appConfig, { platformRef })
```

Jump to details

----
url: https://angular.dev/api/router/QueryParamsHandling
----

```
    
      type QueryParamsHandling = 'merge' | 'preserve' | 'replace' | ''
    
    
```

Jump to details

----
url: https://angular.dev/cli/analytics/prompt
----

```
ng analytics prompt [options]
```

Prompts the user to set the analytics gathering status interactively.

## [Options](#options)

`global`

Alias`g`

Configure analytics gathering and reporting globally in the caller's home directory.

Value Type`boolean`Default`false`

Jump to details

----
url: https://angular.dev/errors/NG0955
----

A track expression specified in the `@for` loop evaluated to duplicated keys for a given collection, ex.:

```
@Component({
  template: `@for (item of items; track item.value) {
    {{ item.value }}
  }`,
})
class Test {
  items = [
    {key: 1, value: 'a'},
    {key: 2, value: 'b'},
    {key: 3, value: 'a'},
  ];
}
```

In the provided example the `item.value` tracking expression will find two duplicate keys `a` (at index 0 and 2).

Duplicate keys are problematic from the correctness point of view: since the `@for` loop can't uniquely identify items it might choose DOM nodes corresponding to *another* item (with the same key) when performing DOM moves or destroy.

There is also performance penalty associated with duplicated keys - internally Angular must use more sophisticated and slower data structures while repeating over collections with duplicated keys.

## [Fixing the error](#fixing-the-error)

Change the tracking expression such that it uniquely identifies an item in a collection. In the discussed example the correct track expression would use the unique `key` property (`item.key`):

```
@Component({
  template: `@for (item of items; track item.key) {
    {{ item.value }}
  }`,
})
class Test {
  items = [
    {key: 1, value: 'a'},
    {key: 2, value: 'b'},
    {key: 3, value: 'a'},
  ];
}
```

----
url: https://angular.dev/api/core/Service
----

```
@Service();
@Service(options: { autoProvided: false });
@Service(options: { autoProvided?: true; factory: () => T });
@Service(options?: { autoProvided?: true });
```

Jump to details

----
url: https://angular.dev/api/forms/signals/FormCheckboxControl
----

```
    
      interface FormCheckboxControl extends FormUiControl<boolean> {}
    
    
```

The checked is the only required property in this contract. A component that wants to integrate with the [`Field`](/api/forms/signals/Field) directive, *must* provide a `model()` that will be kept in sync with the value of the bound [`FieldTree`](/api/forms/signals/FieldTree).

The implementing component *must not* define a `value` property. This is reserved for components that want to integrate with the [`Field`](/api/forms/signals/Field) directive as a standard input.

A contract for a form control that edits a boolean checkbox [`FieldTree`](/api/forms/signals/FieldTree). Any component that implements this contract can be used with the [`Field`](/api/forms/signals/Field) directive.

Many of the properties declared on this contract are optional. They do not need to be implemented, but if they are will be kept in sync with the field state of the field bound to the [`Field`](/api/forms/signals/Field) directive.

Jump to details

----
url: https://angular.dev/api/core/provideAppInitializer
----

```
function provideAppInitializer(initializerFn: () => any): EnvironmentProviders;
```

@paraminitializerFn`() => any`

@returns`EnvironmentProviders`

## [Description](#description)

The provided function is injected at application startup and executed during app initialization. If the function returns a Promise or an Observable, initialization does not complete until the Promise is resolved or the Observable is completed.

You can, for example, create a function that loads language data or an external configuration, and provide that function using [`provideAppInitializer()`](/api/core/provideAppInitializer). The function is executed during the application bootstrap process, and the needed data is available on startup.

Note that the provided initializer is run in the injection context.

Previously, this was achieved using the [`APP_INITIALIZER`](/api/core/APP_INITIALIZER) token which is now deprecated.

## [Usage Notes](#usage-notes)

The following example illustrates how to configure an initialization function using [`provideAppInitializer()`](/api/core/provideAppInitializer)

```
bootstrapApplication(App, {
  providers: [
    provideAppInitializer(() => {
      const http = inject(HttpClient);
      return firstValueFrom(
        http
          .get("https://someUrl.com/api/user")
          .pipe(tap(user => { ... }))
      );
    }),
    provideHttpClient(),
  ],
});
```

Jump to details

----
url: https://angular.dev/api/forms/DefaultValueAccessor
----

```
    
      class DefaultValueAccessor extends BaseControlValueAccessor implements ControlValueAccessor {}
    
    
```

@paramrenderer`Renderer2`

@paramelementRef`ElementRef<any>`

@param\_compositionMode`boolean`

@returns`DefaultValueAccessor`

## [Description](#description)

The default [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing a value and listening to changes on input elements. The accessor is used by the [`FormControlDirective`](/api/forms/FormControlDirective), [`FormControlName`](/api/forms/FormControlName), and [`NgModel`](/api/forms/NgModel) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using the default value accessor](#using-the-default-value-accessor)

The following example shows how to use an input element that activates the default value accessor (in this case, a text field).

```
const firstNameControl = new FormControl();
```

```
<input type="text" [formControl]="firstNameControl">
```

This value accessor is used by default for `<input type="text">` and `<textarea>` elements, but you could also use it for custom components that have similar behavior and do not require special processing. In order to attach the default value accessor to a custom element, add the `ngDefaultControl` attribute as shown below.

```
<custom-input-component ngDefaultControl [(ngModel)]="value"></custom-input-component>
```

Jump to details

----
url: https://angular.dev/api/router/upgrade/RouterUpgradeInitializer
----

```
    
      const RouterUpgradeInitializer: { provide: InjectionToken<readonly ((compRef: ComponentRef<any>) => void)[]>; multi: boolean; useFactory: () => () => void; };
    
    
```

## [Usage Notes](#usage-notes)

For standalone applications:

```
export const appConfig: ApplicationConfig = {
  providers: [RouterUpgradeInitializer],
};
```

For NgModule based applications:

```
@NgModule({
 imports: [
  RouterModule.forRoot(SOME_ROUTES),
  UpgradeModule
],
providers: [
  RouterUpgradeInitializer
]
})
export class AppModule {
  ngDoBootstrap() {}
}
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpBackend
----

## [API](/api/common/http/HttpBackend#api)

```
    
      abstract class HttpBackend implements HttpHandler {}
    
    
```

@paramreq`HttpRequest<any>`

@returns`Observable<HttpEvent<any>>`

## [Description](/api/common/http/HttpBackend#description)

A final [`HttpHandler`](/api/common/http/HttpHandler) which will dispatch the request via browser HTTP APIs to a backend.

Interceptors sit between the [`HttpClient`](/api/common/http/HttpClient) interface and the [`HttpBackend`](/api/common/http/HttpBackend).

When injected, [`HttpBackend`](/api/common/http/HttpBackend) dispatches requests directly to the backend, without going through the interceptor chain.

Jump to details

----
url: https://angular.dev/api/localize/loadTranslations
----

```
function loadTranslations(translations: Record<string, string>): void;
```

@paramtranslations`Record<string, string>`

A map from message ID to translated message.

These messages are processed and added to a lookup based on their [`MessageId`](/api/localize/MessageId).

@returns`void`

## [Description](#description)

Load translations for use by `$localize`, if doing runtime translation.

If the `$localize` tagged strings are not going to be replaced at compiled time, it is possible to load a set of translations that will be applied to the `$localize` tagged strings at runtime, in the browser.

Loading a new translation will overwrite a previous translation if it has the same [`MessageId`](/api/localize/MessageId).

Note that `$localize` messages are only processed once, when the tagged string is first encountered, and does not provide dynamic language changing without refreshing the browser. Loading new translations later in the application life-cycle will not change the translated text of messages that have already been translated.

The message IDs and translations are in the same format as that rendered to "simple JSON" translation files when extracting messages. In particular, placeholders in messages are rendered using the `{$PLACEHOLDER_NAME}` syntax. For example the message from the following template:

```
<div i18n>pre<span>inner-pre<b>bold</b>inner-post</span>post</div>
```

would have the following form in the `translations` map:

```
{
  "2932901491976224757":
     "pre{$START_TAG_SPAN}inner-pre{$START_BOLD_TEXT}bold{$CLOSE_BOLD_TEXT}inner-post{$CLOSE_TAG_SPAN}post"
}
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/DragStartDelay
----

```
    
      type DragStartDelay = number | {touch: number; mouse: number}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/NgLocaleLocalization
----

```
    
      class NgLocaleLocalization extends NgLocalization {}
    
    
```

@paramlocale`string`

@returns`NgLocaleLocalization`

@paramvalue`number`

@paramlocale`string | undefined`

@returns`string`

Jump to details

----
url: https://angular.dev/api/common/http/HttpEvent
----

```
    
      type HttpEvent<T> = | HttpSentEvent  | HttpHeaderResponse  | HttpResponse<T>  | HttpDownloadProgressEvent  | HttpUploadProgressEvent  | HttpUserEvent<T>
    
    
```

## [Description](#description)

Union type for all possible events on the response stream.

Typed according to the expected type of the response.

Jump to details

----
url: https://angular.dev/api/core/globals/Listener
----

```
    
      interface Listener {}
    
    
```

Name of the event listener.

Element that the listener is bound to.

Callback that is invoked when the event is triggered.

Whether the listener is using event capturing.

Type of the listener (e.g. a native DOM event or a custom @Output).

Jump to details

----
url: https://angular.dev/api/forms/signals/submit
----

```
function submit<TModel>(
  form: FieldTree<TModel>,
  options?: NoInfer<FormSubmitOptions<unknown, TModel>> | undefined,
): Promise<boolean>;
function submit<TModel>(
  form: FieldTree<TModel>,
  action: NoInfer<
    (
      field: FieldTree<TModel, string | number, 'writable'>,
      detail: {
        root: FieldTree<unknown, string | number, 'writable'>;
        submitted: FieldTree<TModel, string | number, 'writable'>;
      },
    ) => Promise<TreeValidationResult<WithOptionalFieldTree>>
  >,
): Promise<boolean>;
```

Submits a given [`FieldTree`](/api/forms/signals/FieldTree) using the given action function and applies any submission errors resulting from the action to the field. Submission errors returned by the `action` will be integrated into the field as a [`ValidationError`](/api/forms/signals/ValidationError) on the sub-field indicated by the `fieldTree` property of the submission error.

Concurrent submissions are prohibited. If a submit is already in progress for the given field or any of its parents, subsequent calls to [`submit`](/api/forms/signals/submit) will return `false` immediately without running the action.

@paramform`FieldTree<TModel>`

The field to submit.

@paramoptions`NoInfer<FormSubmitOptions<unknown, TModel>> | undefined`

Options for the submission.

@returns`Promise<boolean>`

Whether the submission was successful.

Usage notes

```
async function registerNewUser(registrationForm: FieldTree<{username: string, password: string}>) {
  const result = await myClient.registerNewUser(registrationForm().value());
  if (result.errorCode === myClient.ErrorCode.USERNAME_TAKEN) {
    return [{
      fieldTree: registrationForm.username,
      kind: 'server',
      message: 'Username already taken'
    }];
  }
  return undefined;
}

const registrationForm = form(signal({username: 'god', password: ''}));
submit(registrationForm, {
  action: async (f) => {
    return registerNewUser(registrationForm);
  }
});
registrationForm.username().errors(); // [{kind: 'server', message: 'Username already taken'}]
```

@paramform`FieldTree<TModel>`

@paramaction`NoInfer<(field: FieldTree<TModel, string | number, "writable">, detail: { root: FieldTree<unknown, string | number, "writable">; submitted: FieldTree<TModel, string | number, "writable">; }) => Promise<TreeValidationResult<WithOptionalFieldTree>>>`

@returns`Promise<boolean>`

## [Description](#description)

Submits a given [`FieldTree`](/api/forms/signals/FieldTree) using the given action function and applies any submission errors resulting from the action to the field. Submission errors returned by the `action` will be integrated into the field as a [`ValidationError`](/api/forms/signals/ValidationError) on the sub-field indicated by the `fieldTree` property of the submission error.

Concurrent submissions are prohibited. If a submit is already in progress for the given field or any of its parents, subsequent calls to [`submit`](/api/forms/signals/submit) will return `false` immediately without running the action.

## [Usage Notes](#usage-notes)

```
async function registerNewUser(registrationForm: FieldTree<{username: string, password: string}>) {
  const result = await myClient.registerNewUser(registrationForm().value());
  if (result.errorCode === myClient.ErrorCode.USERNAME_TAKEN) {
    return [{
      fieldTree: registrationForm.username,
      kind: 'server',
      message: 'Username already taken'
    }];
  }
  return undefined;
}

const registrationForm = form(signal({username: 'god', password: ''}));
submit(registrationForm, {
  action: async (f) => {
    return registerNewUser(registrationForm);
  }
});
registrationForm.username().errors(); // [{kind: 'server', message: 'Username already taken'}]
```

Jump to details

----
url: https://angular.dev/guide/i18n/example
----

## [`fr-CA` and `en-US` example](#fr-ca-and-en-us-example)

The following tabs display the example application and the associated translation files.

### app.component.html

```
<h1 i18n="User welcome|An introduction header for this sample@@introductionHeader">Hello i18n!</h1>

<ng-container i18n>I don't output any element</ng-container>

<br />

<img [src]="logo" i18n-title title="Angular logo" alt="Angular logo" />
<br />
<button type="button" (click)="inc(1)">+</button> <button type="button" (click)="inc(-1)">-</button>
<span i18n
  >Updated
  {minutes, plural, =0 {just now} =1 {one minute ago} other {{{ minutes }} minutes ago}}</span
>
({{ minutes }}) <br /><br />
<button type="button" (click)="male()">&#9794;</button>
<button type="button" (click)="female()">&#9792;</button>
<button type="button" (click)="other()">&#9895;</button>
<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
<br /><br />
<span i18n
  >Updated:
  {minutes, plural,
    =0 {just now}
    =1 {one minute ago}
    other {{{ minutes }} minutes ago by {gender, select, male {male} female {female} other {other}}}
  }
</span>
<br /><br />
<button type="button" (click)="toggleDisplay()">Toggle</button>
<div i18n [attr.aria-label]="toggleAriaLabel()">{{ toggle() }}</div>
```

### app.component.ts

```
import {Component, computed, signal} from '@angular/core';
import {$localize} from '@angular/localize/init';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  minutes = 0;
  gender = 'female';
  fly = true;
  logo = '${this.baseUrl}/angular.svg';
  readonly toggle = signal(false);
  readonly toggleAriaLabel = computed(() => {
    return this.toggle()
      ? $localize`:Toggle Button|A button to toggle status:Show`
      : $localize`:Toggle Button|A button to toggle status:Hide`;
  });

  inc(i: number) {
    this.minutes = Math.min(5, Math.max(0, this.minutes + i));
  }
  male() {
    this.gender = 'male';
  }
  female() {
    this.gender = 'female';
  }
  other() {
    this.gender = 'other';
  }
  toggleDisplay() {
    this.toggle.update((toggle) => !toggle);
  }
}
```

### src/main.ts

```
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';

bootstrapApplication(AppComponent);
```

### src/locale/messages.fr.xlf

```
<!-- The `messages.fr.xlf` after translation for documentation purposes -->
<?xml version="1.0" encoding="UTF-8" ?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" datatype="plaintext" original="ng2.template">
    <body>
      <trans-unit id="introductionHeader" datatype="html">
        <source>Hello i18n!</source>
        <note priority="1" from="description">An introduction header for this sample</note>
        <note priority="1" from="meaning">User welcome</note>
      </trans-unit>
      <trans-unit id="introductionHeader" datatype="html">
        <source>Hello i18n!</source>
        <target>Bonjour i18n !</target>
        <note priority="1" from="description">An introduction header for this sample</note>
        <note priority="1" from="meaning">User welcome</note>
      </trans-unit>
      <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
        <source>I don&apos;t output any element</source>
        <target>Je n'affiche aucun élément</target>
      </trans-unit>
      <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html">
        <source>Angular logo</source>
        <target>Logo d'Angular</target>
      </trans-unit>
      <trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html">
        <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source>
        <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target>
      </trans-unit>
      <trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
        <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
        <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
      </trans-unit>
      <trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
        <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
        <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
      </trans-unit>
      <trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
        <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
        <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
      </trans-unit>
      <trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
        <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
        <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
      </trans-unit>
      <trans-unit id="myId" datatype="html">
        <source>Hello</source>
        <target state="new">Bonjour</target>
      </trans-unit>
    </body>
  </file>
</xliff>
```

----
url: https://angular.dev/api/core/COMPILER_OPTIONS
----

```
    
      const COMPILER_OPTIONS: InjectionToken<CompilerOptions[]>;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/tutorials/signal-forms/5-add-submission
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/4-display-errors)[](/tutorials/signal-forms/6-next-steps)

[](/tutorials/signal-forms/4-display-errors)[](/tutorials/signal-forms/6-next-steps)

Finally, let's learn how to handle form submission. You'll learn how to use the [`submit()`](/api/forms/signals/submit) function to run async operations when the form is valid, and disable the submit button when the form has errors.

In this activity, you'll learn how to:

* Import the [`submit()`](/api/forms/signals/submit) function
* Create a submission handler method
* Use [`submit()`](/api/forms/signals/submit) to run logic only when valid
* Disable the submit button based on form state

Let's complete the form!

***

1. ### [Import the submit function](#import-the-submit-function)

   Import the [`submit`](/api/forms/signals/submit) function from `@angular/forms/signals`:

   ```
   import {form, FormField, required, email, submit} from '@angular/forms/signals';
   ```

2. ### [Add the onSubmit method](#add-the-onsubmit-method)

   In your component class, add an `onSubmit()` method that handles form submission:

   ```
   onSubmit(event: Event) {
     event.preventDefault();
     submit(this.loginForm, async () => {
       const credentials = this.loginModel();
       console.log('Logging in with:', credentials);
       // Add your login logic here
     });
   }
   ```

   The [`submit()`](/api/forms/signals/submit) function only runs your async callback if the form is valid. It also handles the form's submission state automatically.

3. ### [Bind the submit handler to the form](#bind-the-submit-handler-to-the-form)

   In your template, bind the `onSubmit()` method to the form's submit event:

   ```
   <form (submit)="onSubmit($event)"></form>
   ```

4. ### [Disable the button when form is invalid](#disable-the-button-when-form-is-invalid)

   Update the submit button to be disabled when the form is invalid:

   ```
   <button type="submit" [disabled]="loginForm().invalid()">Log in</button>
   ```

   This prevents submission when the form has validation errors.

Congratulations! You've built a complete login form with Signal Forms.

Ready to see what you've learned and explore advanced topics? Continue to [the next steps](/tutorials/signal-forms/6-next-steps)!

----
url: https://angular.dev/api/animations/AnimateTimings
----

```
    
      type AnimateTimings = {  /**   * The full duration of an animation step. A number and optional time unit,   * such as "1s" or "10ms" for one second and 10 milliseconds, respectively.   * The default unit is milliseconds.   */  duration: number;  /**   * The delay in applying an animation step. A number and optional time unit.   * The default unit is milliseconds.   */  delay: number;  /**   * An easing style that controls how an animations step accelerates   * and decelerates during its run time. An easing function such as `cubic-bezier()`,   * or one of the following constants:   * - `ease-in`   * - `ease-out`   * - `ease-in-and-out`   */  easing: string | null;}
    
    
```

Jump to details

----
url: https://angular.dev/api/aria/grid/GridCellWidget
----

```
    
      class GridCellWidget {}
    
    
```

A reference to the host element.

Whether the widget is currently active (focused).

A unique identifier for the widget.

The type of widget, which determines how it is activated.

Whether the widget is disabled.

The target that will receive focus instead of the widget.

Emits when the widget is activated.

Emits when the widget is deactivated.

The tabindex override.

Whether the widget is activated.

Activates the widget.

@returns`void`

Deactivates the widget.

@returns`void`

## [Description](#description)

Represents an interactive element inside a [`GridCell`](/api/aria/grid/GridCell). It allows for pausing grid navigation to interact with the widget.

When the user interacts with the widget (e.g., by typing in an input or opening a menu), grid navigation is temporarily suspended to allow the widget to handle keyboard events.

```
<td ngGridCell>
  <button ngGridCellWidget>Click Me</button>
</td>
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpClientXsrfModule
----

Use withXsrfConfiguration({cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN'}) as providers instead or [`withNoXsrfProtection`](/api/common/http/withNoXsrfProtection) if you want to disabled XSRF protection.

## [API](/api/common/http/HttpClientXsrfModule#api)

```
    
      class HttpClientXsrfModule {}
    
    
```

Disable the default XSRF protection.

@returns`ModuleWithProviders<HttpClientXsrfModule>`

Configure XSRF protection.

@paramoptions`{ cookieName?: string | undefined; headerName?: string | undefined; }`

An object that can specify either or both cookie name or header name.

* Cookie name default is `XSRF-TOKEN`.
* Header name default is `X-XSRF-TOKEN`.

@returns`ModuleWithProviders<HttpClientXsrfModule>`

## [Description](/api/common/http/HttpClientXsrfModule#description)

Configures XSRF protection support for outgoing requests.

For a server that supports a cookie-based XSRF protection system, use directly to configure XSRF protection with the correct cookie and header names.

If no names are supplied, the default cookie name is `XSRF-TOKEN` and the default header name is `X-XSRF-TOKEN`.

Jump to details

----
url: https://angular.dev/api/core/provideBrowserGlobalErrorListeners
----

```
function provideBrowserGlobalErrorListeners(): EnvironmentProviders;
```

@returns`EnvironmentProviders`

## [Description](#description)

Provides an environment initializer which forwards unhandled errors to the ErrorHandler.

The listeners added are for the window's 'unhandledrejection' and 'error' events.

Jump to details

----
url: https://angular.dev/api/forms/FormArray
----

```
    
      class FormArray<TControl extends AbstractControl<any> = any> extends AbstractControl<  ɵTypedOrUntyped<TControl, ɵFormArrayValue<TControl>, any>,  ɵTypedOrUntyped<TControl, ɵFormArrayRawValue<TControl>, any>> {
}
    
    
```

Creates a new [`FormArray`](/api/forms/FormArray) instance.

@paramcontrols`TControl[]`

An array of child controls. Each child control is given an index where it is registered.

@paramvalidatorOrOpts`ValidatorFn | AbstractControlOptions | ValidatorFn[] | null | undefined`

A synchronous validator function, or an array of such functions, or an [`AbstractControlOptions`](/api/forms/AbstractControlOptions) object that contains validation functions and a validation trigger.

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

A single async validator or array of async validator functions

@returns`FormArray<TControl>`

Get the [`AbstractControl`](/api/forms/AbstractControl) at the given `index` in the array.

@paramindex`number`

Index in the array to retrieve the control. If `index` is negative, it will wrap around from the back, and if index is greatly negative (less than `-length`), the result is undefined. This behavior is the same as `Array.at(index)`.

@returns`ɵTypedOrUntyped<TControl, TControl, AbstractControl<any, any, any>>`

Insert a new [`AbstractControl`](/api/forms/AbstractControl) at the end of the array.

@paramcontrol`TControl | TControl[]`

Form control to be inserted

@paramoptions`{ emitEvent?: boolean | undefined; }`

Specifies whether this FormArray instance should emit events after a new control is added.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is inserted. When false, no events are emitted.

**NOTE:** Pushing to the FormArray will not mark it dirty. If you want to mark if dirty, call `markAsDirty()`.

@returns`void`

Insert a new [`AbstractControl`](/api/forms/AbstractControl) at the given `index` in the array.

@paramindex`number`

Index in the array to insert the control. If `index` is negative, wraps around from the back. If `index` is greatly negative (less than `-length`), prepends to the array. This behavior is the same as `Array.splice(index, 0, control)`.

@paramcontrol`TControl`

Form control to be inserted

@paramoptions`{ emitEvent?: boolean | undefined; }`

Specifies whether this FormArray instance should emit events after a new control is inserted.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is inserted. When false, no events are emitted.

**NOTE:** Inserting to the FormArray will not mark it dirty. If you want to mark if dirty, call `markAsDirty()`.

@returns`void`

Remove the control at the given `index` in the array.

@paramindex`number`

Index in the array to remove the control. If `index` is negative, wraps around from the back. If `index` is greatly negative (less than `-length`), removes the first element. This behavior is the same as `Array.splice(index, 1)`.

@paramoptions`{ emitEvent?: boolean | undefined; }`

Specifies whether this FormArray instance should emit events after a control is removed.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is removed. When false, no events are emitted.

**NOTE:** Removing the FormArray will not mark it dirty. If you want to mark if dirty, call `markAsDirty()`.

@returns`void`

Replace an existing control.

@paramindex`number`

Index in the array to replace the control. If `index` is negative, wraps around from the back. If `index` is greatly negative (less than `-length`), replaces the first element. This behavior is the same as `Array.splice(index, 1, control)`.

@paramcontrol`TControl`

The [`AbstractControl`](/api/forms/AbstractControl) control to replace the existing control

@paramoptions`{ emitEvent?: boolean | undefined; }`

Specifies whether this FormArray instance should emit events after an existing control is replaced with a new one.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is replaced with a new one. When false, no events are emitted.

@returns`void`

Length of the control array.

Sets the value of the [`FormArray`](/api/forms/FormArray). It accepts an array that matches the structure of the control.

This method performs strict checks, and throws an error if you try to set the value of a control that doesn't exist or if you exclude the value of a control.

@paramvalue`ɵIsAny<TControl, any[], ɵRawValue<TControl>[]>`

Array of values for the controls

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; }`

Configure options that determine how the control propagates changes and emits events after the value changes

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control value is updated. When false, no events are emitted. The configuration options are passed to the [`* updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

@returns`void`

Usage notes

### [Set the values for the controls in the form array](#set-the-values-for-the-controls-in-the-form-array)

```
const arr = new FormArray([
  new FormControl(),
  new FormControl()
]);
console.log(arr.value);   // [null, null]

arr.setValue(['Nancy', 'Drew']);
console.log(arr.value);   // ['Nancy', 'Drew']
```

Patches the value of the [`FormArray`](/api/forms/FormArray). It accepts an array that matches the structure of the control, and does its best to match the values to the correct controls in the group.

It accepts both super-sets and sub-sets of the array without throwing an error.

@paramvalue`ɵIsAny<TControl, any[], ɵValue<TControl>[]>`

Array of latest values for the controls

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; }`

Configure options that determine how the control propagates changes and emits events after the value changes

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control value is updated. When false, no events are emitted. The configuration options are passed to the [`updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

@returns`void`

Usage notes

### [Patch the values for controls in a form array](#patch-the-values-for-controls-in-a-form-array)

```
const arr = new FormArray([
   new FormControl(),
   new FormControl()
]);
console.log(arr.value);   // [null, null]

arr.patchValue(['Nancy']);
console.log(arr.value);   // ['Nancy', null]
```

Resets the [`FormArray`](/api/forms/FormArray) and all descendants are marked `pristine` and `untouched`, and the value of all descendants to null or null maps.

You reset to a specific form state by passing in an array of states that matches the structure of the control. The state is a standalone value or a form state object with both a value and a disabled status.

@paramvalue`ɵTypedOrUntyped<TControl, ɵIsAny<TControl, any[], ɵValue<TControl>[]>, any>`

Array of values for the controls

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; overwriteDefaultValue?: boolean | undefined; }`

Configure options that determine how the control propagates changes and emits events after the value changes

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is reset. When false, no events are emitted. The configuration options are passed to the [`* updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

@returns`void`

Usage notes

### [Reset the values in a form array](#reset-the-values-in-a-form-array)

```
const arr = new FormArray([
   new FormControl(),
   new FormControl()
]);
arr.reset(['name', 'last name']);

console.log(arr.value);  // ['name', 'last name']
```

### [Reset the values in a form array and the disabled status for the first control](#reset-the-values-in-a-form-array-and-the-disabled-status-for-the-first-control)

```
arr.reset([
  {value: 'name', disabled: true},
  'last'
]);

console.log(arr.value);  // ['last']
console.log(arr.at(0).status);  // 'DISABLED'
```

The aggregate value of the array, including any disabled controls.

Reports all values regardless of disabled status.

@returns`ɵIsAny<TControl, any[], ɵRawValue<TControl>[]>`

Remove all controls in the [`FormArray`](/api/forms/FormArray).

@paramoptions`{ emitEvent?: boolean | undefined; }`

Specifies whether this FormArray instance should emit events after all controls are removed.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when all controls in this FormArray instance are removed. When false, no events are emitted.

@returns`void`

Usage notes

### [Remove all elements from a FormArray](#remove-all-elements-from-a-formarray)

```
const arr = new FormArray([
   new FormControl(),
   new FormControl()
]);
console.log(arr.length);  // 2

arr.clear();
console.log(arr.length);  // 0
```

It's a simpler and more efficient alternative to removing all elements one by one:

```
const arr = new FormArray([
   new FormControl(),
   new FormControl()
]);

while (arr.length) {
   arr.removeAt(0);
}
```

```
// Reference to the RequiredValidator
const ctrl = new FormControl<string | null>('', Validators.required);
ctrl.removeValidators(Validators.required);

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<string | null>('', minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)

ctrl.removeValidators(minValidator);
```

```
// Reference to the RequiredValidator
const ctrl = new FormControl<number | null>(0, Validators.required);
expect(ctrl.hasValidator(Validators.required)).toEqual(true)

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<number | null>(0, minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)
```

```
const login = new FormControl('someLogin');
login.setErrors({
  notUnique: true
});

expect(login.valid).toEqual(false);
expect(login.errors).toEqual({ notUnique: true });

login.setValue('someOtherLogin');

expect(login.valid).toEqual(true);
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

Tracks the value and validity state of an array of [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray) instances.

A [`FormArray`](/api/forms/FormArray) aggregates the values of each child [`FormControl`](/api/forms/FormControl) into an array. It calculates its status by reducing the status values of its children. For example, if one of the controls in a [`FormArray`](/api/forms/FormArray) is invalid, the entire array becomes invalid. Similarly, if all controls in a [`FormArray`](/api/forms/FormArray) are disabled, the entire array becomes disabled.

[`FormArray`](/api/forms/FormArray) accepts one generic argument, which is the type of the controls inside. If you need a heterogenous array, use [`UntypedFormArray`](/api/forms/UntypedFormArray).

[`FormArray`](/api/forms/FormArray) is one of the four fundamental building blocks used to define forms in Angular, along with [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup), and [`FormRecord`](/api/forms/FormRecord).

## [Usage Notes](#usage-notes)

### [Create an array of form controls](#create-an-array-of-form-controls)

```
const arr = new FormArray([
  new FormControl('Nancy', Validators.minLength(2)),
  new FormControl('Drew'),
]);

console.log(arr.value);   // ['Nancy', 'Drew']
console.log(arr.status);  // 'VALID'
```

### [Create a form array with array-level validators](#create-a-form-array-with-array-level-validators)

You include array-level validators and async validators. These come in handy when you want to perform validation that considers the value of more than one child control.

The two types of validators are passed in separately as the second and third arg respectively, or together as part of an options object.

```
const arr = new FormArray([
  new FormControl('Nancy'),
  new FormControl('Drew')
], {validators: myValidator, asyncValidators: myAsyncValidator});
```

### [Set the updateOn property for all controls in a form array](#set-the-updateon-property-for-all-controls-in-a-form-array)

The options object is used to set a default value for each child control's `updateOn` property. If you set `updateOn` to `'blur'` at the array level, all child controls default to 'blur', unless the child has explicitly specified a different `updateOn` value.

```
const arr = new FormArray([
   new FormControl()
], {updateOn: 'blur'});
```

### [Adding or removing controls from a form array](#adding-or-removing-controls-from-a-form-array)

To change the controls in the array, use the `push`, `insert`, `removeAt` or `clear` methods in [`FormArray`](/api/forms/FormArray) itself. These methods ensure the controls are properly tracked in the form's hierarchy. Do not modify the array of [`AbstractControl`](/api/forms/AbstractControl)s used to instantiate the [`FormArray`](/api/forms/FormArray) directly, as that result in strange and unexpected behavior such as broken change detection.

Jump to details

----
url: https://angular.dev/api/forms/UntypedFormControl
----

## [API](/api/forms/UntypedFormControl#api)

```
    
      type UntypedFormControl = FormControl<any>
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser-dynamic/testing/BrowserDynamicTestingModule
----

Use the [`BrowserTestingModule`](/api/platform-browser/testing/BrowserTestingModule) from `@angular/platform-browser/testing` instead.

## [API](#api)

```
    
      class BrowserDynamicTestingModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/ssr/createRequestHandler
----

```
function createRequestHandler(
  handler: RequestHandlerFunction,
): RequestHandlerFunction;
```

@paramhandler`RequestHandlerFunction`

* The request handler function to be annotated.

@returns`RequestHandlerFunction`

The same handler function passed in, with metadata attached.

## [Usage Notes](#usage-notes)

Example usage in a Hono application:

```
const app = new Hono();
export default createRequestHandler(app.fetch);
```

Example usage in a H3 application:

```
const app = createApp();
const handler = toWebHandler(app);
export default createRequestHandler(handler);
```

Jump to details

----
url: https://angular.dev/cli/completion
----

```
ng completion [options]
```

Setting up autocompletion configures your terminal, so pressing the `<TAB>` key while in the middle of typing will display various commands and options available to you. This makes it very easy to discover and use CLI commands without lots of memorization.

## [Automated setup](#automated-setup)

The CLI should prompt and ask to set up autocompletion for you the first time you use it (v14+). Simply answer "Yes" and the CLI will take care of the rest.

```
$ ng serve
? Would you like to enable autocompletion? This will set up your terminal so pressing TAB while typing Angular CLI commands will show possible options and autocomplete arguments. (Enabling autocompletion will modify configuration files in your home directory.) Yes
Appended `source <(ng completion script)` to `/home/my-username/.bashrc`. Restart your terminal or run:

source <(ng completion script)

to autocomplete `ng` commands.

# Serve output...
```

If you already refused the prompt, it won't ask again. But you can run `ng completion` to do the same thing automatically.

This modifies your terminal environment to load Angular CLI autocompletion, but can't update your current terminal session. Either restart it or run `source <(ng completion script)` directly to enable autocompletion in your current session.

Test it out by typing `ng ser<TAB>` and it should autocomplete to `ng serve`. Ambiguous arguments will show all possible options and their documentation, such as `ng generate <TAB>`.

## [Manual setup](#manual-setup)

Some users may have highly customized terminal setups, possibly with configuration files checked into source control with an opinionated structure. `ng completion` only ever appends Angular's setup to an existing configuration file for your current shell, or creates one if none exists. If you want more control over exactly where this configuration lives, you can manually set it up by having your shell run at startup:

```
source <(ng completion script)
```

This is equivalent to what `ng completion` will automatically set up, and gives power users more flexibility in their environments when desired.

## [Platform support](#platform-support)

Angular CLI supports autocompletion for the Bash and Zsh shells on MacOS and Linux operating systems. On Windows, Git Bash and [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) using Bash or Zsh are supported.

## [Global install](#global-install)

Autocompletion works by configuring your terminal to invoke the Angular CLI on startup to load the setup script. This means the terminal must be able to find and execute the Angular CLI, typically through a global install that places the binary on the user's `$PATH`. If you get `command not found: ng`, make sure the CLI is installed globally which you can do with the `-g` flag:

```
npm install -g @angular/cli
```

### Sub-commands

This command has the following sub-commands

* [script](cli/completion/script)

## [Options](#options)

`help`

Shows a help message for this command in the console.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/errors/NG8003
----

Angular can't find a directive with `{{ PLACEHOLDER }}` export name. This is common with a missing import or a missing [`exportAs`](api/core/Directive#exportAs) on a directive.

**HELPFUL:** This is the compiler equivalent of a common runtime error [NG0301: Export Not Found](errors/NG0301).

## [Debugging the error](#debugging-the-error)

Use the string name of the export not found to trace the templates or modules using this export.

Ensure that all dependencies are properly imported and declared in our Modules. For example, if the export not found is [`ngForm`](/api/forms/NgForm), we will need to import [`FormsModule`](/api/forms/FormsModule) and declare it in our list of imports in `*.module.ts` to resolve the missing export error.

```
import { FormsModule } from '@angular/forms';

@NgModule({
…
imports: [
FormsModule,
…
```

If you recently added an import, you will need to restart your server to see these changes.

----
url: https://angular.dev/api/common/XhrFactory
----

```
    
      abstract class XhrFactory {}
    
    
```

@returns`XMLHttpRequest`

Jump to details

----
url: https://angular.dev/api/core/@defer
----

The `@defer` block can be used to lazy load components, directives and pipes used inside a component template.

## [Syntax](#syntax)

```
@defer (on <trigger>; when <condition>; prefetch on <trigger>; prefetch when <condition>) {
  <!-- deferred template fragment -->
  <calendar-cmp />
} @placeholder (minimum? <duration>) {
  <!-- placeholder template fragment -->
  <p>Placeholder</p>
} @loading (minimum? <duration>; after? <duration>) {
  <!-- loading template fragment -->
  <img alt="loading image" src="loading.gif" />
} @error {
  <!-- error template fragment -->
  <p>An loading error occurred</p>
}
```

## [Description](#description)

### [Blocks](#blocks)

Supported sections of a defer block. Note: only the @defer block template fragment is deferred loaded. The remaining optional blocks are eagerly loaded.

| block          | Description                                              |
| -------------- | -------------------------------------------------------- |
| `@defer`       | The defer loaded block of content                        |
| `@placeholder` | Content shown prior to defer loading (Optional)          |
| `@loading`     | Content shown during defer loading (Optional)            |
| `@error`       | Content shown when defer loading errors occur (Optional) |

### Triggers

Triggers provide conditions for when defer loading occurs. Some allow a template reference variable as an optional parameter. Separate multiple triggers with a semicolon.

| trigger                         | Triggers...                                   |
| ------------------------------- | --------------------------------------------- |
| `on idle(<timeout>?)`           | when the browser reports idle state (default) |
| `on viewport(<elementRef>?)`    | when the element enters the viewport          |
| `on interaction(<elementRef>?)` | when clicked, touched, or focused             |
| `on hover(<elementRef>?)`       | when element has been hovered                 |
| `on immediate`                  | when the page finishes rendering              |
| `on timer(<duration>)`          | after a specific timeout                      |
| `when <condition>`              | on a custom condition                         |

## Prefetch

Configures prefetching of the defer block used in the `@defer` parameters, but does not affect rendering. Rendering is handled by the standard `on` and `when` conditions. Separate multiple prefetch configurations with a semicolon.

```
@defer (prefetch on <trigger>; prefetch when <condition>) {
  <!-- deferred template fragment -->
}
```

**HELPFUL:** Learn more in the [defer loading guide](/guide/templates/defer).

Jump to details

----
url: https://angular.dev/api/router/CanActivateChild
----

```
    
      interface CanActivateChild {}
    
    
```

@paramchildRoute`ActivatedRouteSnapshot`

@paramstate`RouterStateSnapshot`

@returns`MaybeAsync<GuardResult>`

## [Description](#description)

Interface that a class can implement to be a guard deciding if a child route can be activated. If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), current navigation is cancelled and a new navigation begins to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements a [`CanActivateChild`](/api/router/CanActivateChild) function that checks whether the current user has permission to activate the requested child route.

```
class UserToken {}
class Permissions {
  canActivate(user: UserToken, id: string): boolean {
    return true;
  }
}

@Injectable()
class CanActivateTeam implements CanActivateChild {
  private readonly permissions = inject(Permissions);
  private readonly currentUser = inject(UserToken);

  canActivateChild(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): MaybeAsync<GuardResult> {
    return this.permissions.canActivate(this.currentUser, route.params.id);
  }
}
```

Here, the defined guard function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'root',
        canActivateChild: [CanActivateTeam],
        children: [
          {
             path: 'team/:id',
             component: TeamComponent
          }
        ]
      }
    ])
  ],
  providers: [CanActivateTeam, UserToken, Permissions]
})
class AppModule {}
```

Jump to details

----
url: https://angular.dev/api/router/RouterResourcesFeature
----

```
    
      type RouterResourcesFeature = RouterFeature<RouterFeatureKind.RouterResourcesFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/LimitSelectionKey
----

```
    
      type LimitSelectionKey = MetadataKey<  Signal<LimitKey<unknown> | undefined>,  LimitKey<unknown>,  LimitKey<unknown> | undefined> & {  [LIMIT_SELECTION_KEY]: true;}
    
    
```

## [Description](#description)

Used to select a [`LimitKey`](/api/forms/signals/LimitKey).

This indirection allows rules to bind a [`LimitKey`](/api/forms/signals/LimitKey) of a specific limit type (e.g. `number` or `Date`) matching the field's type to a generic [`MetadataKey`](/api/forms/signals/MetadataKey).

Jump to details

----
url: https://angular.dev/api/forms/signals/ValidationError
----

```
    
      interface ValidationError {}
    
    
```

Identifies the kind of error.

Human readable error message.

Validation error with an associated field tree.

This is returned from field state, e.g., catField.errors() would be of a list of errors with `field: catField` bound to state.

```
    
      interface WithFieldTree extends ValidationError {}
    
    
```

@deprecated

Use [`ValidationError.WithFieldTree`](/api/forms/signals/ValidationError#WithFieldTree) instead

Validation error with an associated field tree and specific form field binding.

```
    
      interface WithFormField extends WithFieldTree {}
    
    
```

Validation error with optional field.

This is generally used in places where the result might have a field. e.g., as a result of a [`validateTree`](/api/forms/signals/validateTree), or when handling form submission.

```
    
      interface WithOptionalFieldTree extends ValidationError {}
    
    
```

@deprecated

Use [`ValidationError.WithOptionalFieldTree`](/api/forms/signals/ValidationError#WithOptionalFieldTree) instead

Validation error with no field.

This is used to strongly enforce that fields are not allowed in validation result.

```
    
      interface WithoutFieldTree extends ValidationError {}
    
    
```

@deprecated

Use [`ValidationError.WithoutFieldTree`](/api/forms/signals/ValidationError#WithoutFieldTree) instead

## [Description](#description)

Common interface for all validation errors.

This can be returned from validators.

It's also used by the creation functions to create an instance (e.g. [`requiredError`](/api/forms/signals/requiredError), [`minError`](/api/forms/signals/minError), etc.).

Jump to details

----
url: https://angular.dev/api/ssr/node/isMainModule
----

```
function isMainModule(url: string): boolean;
```

@paramurl`string`

The URL of the module to check. This should typically be `import.meta.url`.

@returns`boolean`

`true` if the provided URL represents the main entry point, otherwise `false`.

## [Description](#description)

Determines whether the provided URL represents the main entry point module.

This function checks if the provided URL corresponds to the main ESM module being executed directly. It's useful for conditionally executing code that should only run when a module is the entry point, such as starting a server or initializing an application.

It performs two key checks:

1. Verifies if the URL starts with 'file:', ensuring it is a local file.
2. Compares the URL's resolved file path with the first command-line argument (`process.argv[1]`), which points to the file being executed.

Jump to details

----
url: https://angular.dev/api/core/Injectable
----

```
@Injectable();
@Injectable(
  options?: { providedIn: Type<any> | 'any' } & InjectableProvider,
);
@Injectable(
  options?: { providedIn: 'root' | 'platform' | null } & InjectableProvider,
);
```

## [Usage Notes](#usage-notes)

Marking a class with [`@Injectable`](/api/core/Injectable) ensures that the compiler will generate the necessary metadata to create the class's dependencies when the class is injected.

The following example shows how a service class is properly marked so that a supporting service can be injected upon creation.

```
@Injectable()
        class UsefulService {}

        @Injectable()
        class NeedsService {
          constructor(public service: UsefulService) {}
        }

        const injector = Injector.create({
          providers: [
            {provide: NeedsService, deps: [UsefulService]},
            {provide: UsefulService, deps: []},
          ],
        });
        expect(injector.get(NeedsService).service instanceof UsefulService).toBe(true);
```

Jump to details

----
url: https://angular.dev/api/common/SlicePipe
----

```
{{ value_expression | slice:start:end }}
```

## [API](#api)

```
    
      class SlicePipe implements PipeTransform {
}
    
    
```

@paramvalue`readonly T[]`

a list or a string to be sliced.

@paramstart`number`

the starting index of the subset to return:

* **a positive integer**: return the item at `start` index and all items after in the list or string expression.
* **a negative integer**: return the item at `start` index from the end and all items after in the list or string expression.
* **if positive and greater than the size of the expression**: return an empty list or string.
* **if negative and greater than the size of the expression**: return entire list or string.

@paramend`number | undefined`

the ending index of the subset to return:

* **omitted**: return all items until the end.
* **if positive**: return all items before `end` index of the list or string.
* **if negative**: return all items before `end` index from the end of the list or string.

@returns`T[]`

@paramvalue`null | undefined`

@paramstart`number`

@paramend`number | undefined`

@returns`null`

@paramvalue`readonly T[] | null | undefined`

@paramstart`number`

@paramend`number | undefined`

@returns`T[] | null`

@paramvalue`string`

@paramstart`number`

@paramend`number | undefined`

@returns`string`

@paramvalue`string | null | undefined`

@paramstart`number`

@paramend`number | undefined`

@returns`string | null`

## [Description](#description)

Creates a new `Array` or `String` containing a subset (slice) of the elements.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

All behavior is based on the expected behavior of the JavaScript API `Array.prototype.slice()` and `String.prototype.slice()`.

When operating on an `Array`, the returned `Array` is always a copy even when all the elements are being returned.

When operating on a blank value, the pipe returns the blank value.

### [List Example](#list-example)

This `ngFor` example:

```
@Component({
  selector: 'slice-list-pipe',
  imports: [SlicePipe],
  template: `<ul>
    @for (i of collection | slice: 1 : 3; track $index) {
      <li>{{ i }}</li>
    }
  </ul>`,
})
export class SlicePipeListComponent {
  collection: string[] = ['a', 'b', 'c', 'd'];
}
```

produces the following:

```
<li>b</li>
<li>c</li>
```

### [String Examples](#string-examples)

```
@Component({
  selector: 'slice-string-pipe',
  imports: [SlicePipe],
  template: `<div>
    <p>{{ str }}[0:4]: '{{ str | slice: 0 : 4 }}' - output is expected to be 'abcd'</p>
    <p>{{ str }}[4:0]: '{{ str | slice: 4 : 0 }}' - output is expected to be ''</p>
    <p>{{ str }}[-4]: '{{ str | slice: -4 }}' - output is expected to be 'ghij'</p>
    <p>{{ str }}[-4:-2]: '{{ str | slice: -4 : -2 }}' - output is expected to be 'gh'</p>
    <p>{{ str }}[-100]: '{{ str | slice: -100 }}' - output is expected to be 'abcdefghij'</p>
    <p>{{ str }}[100]: '{{ str | slice: 100 }}' - output is expected to be ''</p>
  </div>`,
})
export class SlicePipeStringComponent {
  str: string = 'abcdefghij';
}
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/createDragRef
----

```
function createDragRef<T = unknown>(
  injector: Injector,
  element: any,
  config?: DragRefConfig,
): DragRef<T>;
```

@paraminjector`Injector`

Injector used to resolve dependencies.

@paramelement`any`

Element to which to attach the dragging functionality.

@paramconfig`DragRefConfig`

Object used to configure the dragging behavior.

@returns`DragRef<T>`

Jump to details

----
url: https://angular.dev/api/core/DEFAULT_CURRENCY_CODE
----

```
    
      const DEFAULT_CURRENCY_CODE: InjectionToken<string>;
    
    
```

## [Description](#description)

Provide this token to set the default currency code your application uses for CurrencyPipe when there is no currency code passed into it. This is only used by CurrencyPipe and has no relation to locale currency. Defaults to USD if not configured.

See the [i18n guide](guide/i18n/locale-id) for more information.

The default currency code is currently always `USD`.

If you need the previous behavior then set it by creating a [`DEFAULT_CURRENCY_CODE`](/api/core/DEFAULT_CURRENCY_CODE) provider in your application [`NgModule`](/api/core/NgModule):

```
{provide: DEFAULT_CURRENCY_CODE, useValue: 'USD'}
```

## [Usage Notes](#usage-notes)

### [Example](#example)

In standalone apps:

```
import { LOCALE_ID, ApplicationConfig } from '@angular/core';

const appConfig: ApplicationConfig = {
  providers: [{provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR' }]
};
```

In module based apps:

```
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app.module';

platformBrowser().bootstrapModule(AppModule, {
  providers: [{provide: DEFAULT_CURRENCY_CODE, useValue: 'EUR' }]
});
```

Jump to details

----
url: https://angular.dev/tutorials/signals/7-using-signals-with-services
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/6-two-way-binding-with-model-signals)[](/tutorials/signals/8-using-signals-with-directives)

[](/tutorials/signals/6-two-way-binding-with-model-signals)[](/tutorials/signals/8-using-signals-with-directives)

Now that you've learned [two-way binding with model signals](/tutorials/signals/6-two-way-binding-with-model-signals), let's explore how to use signals with Angular services. Services are perfect for sharing reactive state across multiple components, and signals make this even more powerful by providing automatic change detection and clean reactive patterns.

In this activity, you'll learn how to create a cart store with signals that allow the cart display component to react to state changes automatically.

***

1. ### [Add cart store signals](#add-cart-store-signals)

   Add readonly and computed signals to make the cart state reactive in `cart-store.ts`.

   ```
   // Add the computed import
   import {Service, signal, computed} from '@angular/core';

   // Then add these signals to the class:

   // Readonly signals
   readonly cartItems = this.items.asReadonly();

   // Computed signals
   readonly totalQuantity = computed(() => {
     return this.items().reduce((sum, item) => sum + item.quantity, 0);
   });

   readonly totalPrice = computed(() => {
     return this.items().reduce((sum, item) => sum + item.price * item.quantity, 0);
   });
   ```

   These signals allow components to reactively access cart data and computed totals. The `asReadonly()` method prevents external code from modifying the cart items directly, while [`computed()`](/api/core/computed) creates derived state that automatically updates when the source signal changes.

2. ### [Complete the quantity update methods](#complete-the-quantity-update-methods)

   The cart display component in `cart-display.ts` already uses the cart store signals in its template. Complete the quantity update methods to modify cart items:

   ```
   increaseQuantity(id: string) {
     const items = this.cartStore.cartItems();
     const currentItem = items.find((item) => item.id === id);
     if (currentItem) {
       this.cartStore.updateQuantity(id, currentItem.quantity + 1);
     }
   }

   decreaseQuantity(id: string) {
     const items = this.cartStore.cartItems();
     const currentItem = items.find((item) => item.id === id);
     if (currentItem && currentItem.quantity > 1) {
       this.cartStore.updateQuantity(id, currentItem.quantity - 1);
     }
   }
   ```

   These methods read the current cart state using `cartItems()` and update quantities through the store's methods. The UI automatically updates when the signals change!

3. ### [Update the main app component](#update-the-main-app-component)

   Update the main app component in `app.ts` to use the cart service and display the cart component.

   ```
   import {Component, inject} from '@angular/core';
   import {CartStore} from './cart-store';
   import {CartDisplay} from './cart-display';

   @Component({
     selector: 'app-root',
     imports: [CartDisplay],
     template: `
       <div class="shopping-app">
         <header>
           <h1>Signals with Services Demo</h1>
           <div class="cart-badge">
             Cart: {{ cartStore.totalQuantity() }} items (\${{ cartStore.totalPrice() }})
           </div>
         </header>

         <main>
           <cart-display />
         </main>
       </div>
     `,
     styleUrl: './app.css',
   })
   export class App {
     cartStore = inject(CartStore);
   }
   ```

Excellent! You've now learned how to use signals with services. Key concepts to remember:

* **Service-level signals**: Services can use signals to manage reactive state
* **Dependency injection**: Use `inject()` to access services with signals in components
* **Computed signals in services**: Create derived state that updates automatically
* **Readonly signals**: Expose read-only versions of signals to prevent external mutations

In the next lesson, you'll learn about [how to use signals with directives](/tutorials/signals/8-using-signals-with-directives)!

----
url: https://angular.dev/api/core/testing/ComponentFixture
----

```
    
      class ComponentFixture<T> extends AbstractFixture<any> {
}
    
    
```

The instance of the root component class.

Retrieves all defer block fixtures in the component fixture.

@returns`Promise<DeferBlockFixture[]>`

----
url: https://angular.dev/api/forms/signals/metadata
----

```
function metadata<
  TValue,
  TKey extends MetadataKey<any, any, any>,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  key: TKey,
  logic: NoInfer<
    LogicFn<
      TValue,
      TKey extends LimitSelectionKey
        ? LimitKey<TValue>
        : MetadataSetterType<TKey>,
      TPathKind
    >
  >,
): TKey;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to set the metadata for.

@paramkey`TKey`

The metadata key

@paramlogic`NoInfer<LogicFn<TValue, TKey extends LimitSelectionKey ? LimitKey<TValue> : MetadataSetterType<TKey>, TPathKind>>`

A function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns a value for the metadata.

@returns`TKey`

## [Description](#description)

Sets a value for the [`MetadataKey`](/api/forms/signals/MetadataKey) for this field.

This value is combined via a reduce operation defined by the particular key, since multiple rules in the schema might set values for it.

Jump to details

----
url: https://angular.dev/api/router/CanMatch
----

```
    
      interface CanMatch {}
    
    
```

@paramroute`Route`

@paramsegments`UrlSegment[]`

@paramcurrentSnapshot`PartialMatchRouteSnapshot`

@returns`MaybeAsync<GuardResult>`

## [Description](#description)

Interface that a class can implement to be a guard deciding if a [`Route`](/api/router/Route) can be matched. If all guards return `true`, navigation continues and the [`Router`](/api/router/Router) will use the [`Route`](/api/router/Route) during activation. If any guard returns `false`, the [`Route`](/api/router/Route) is skipped for matching and other [`Route`](/api/router/Route) configurations are processed instead.

The following example implements a [`CanMatch`](/api/router/CanMatch) function that decides whether the current user has permission to access the users page.

```
class UserToken {}
class Permissions {
  canAccess(user: UserToken, route: Route, segments: UrlSegment[]): boolean {
    return true;
  }
}

@Injectable()
class CanMatchTeamSection implements CanMatch {
  private readonly permissions = inject(Permissions);
  private readonly currentUser = inject(UserToken);

  canMatch(
    route: Route,
    segments: UrlSegment[],
    currentSnapshot: PartialMatchRouteSnapshot,
  ): Observable<boolean> | Promise<boolean> | boolean {
    return this.permissions.canAccess(this.currentUser, route, segments);
  }
}
```

Here, the defined guard function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'team/:id',
        component: TeamComponent,
        loadChildren: () => import('./team').then(mod => mod.TeamModule),
        canMatch: [CanMatchTeamSection]
      },
      {
        path: '**',
        component: NotFoundComponent
      }
    ])
  ],
  providers: [CanMatchTeamSection, UserToken, Permissions]
})
class AppModule {}
```

If the `CanMatchTeamSection` were to return `false`, the router would continue navigating to the `team/:id` URL, but would load the `NotFoundComponent` because the [`Route`](/api/router/Route) for `'team/:id'` could not be used for a URL match but the catch-all `**` [`Route`](/api/router/Route) did instead.

Jump to details

----
url: https://angular.dev/cli/cache
----

```
ng cache [options]
```

Angular CLI saves a number of cachable operations on disk by default.

When you re-run the same build, the build system restores the state of the previous build and re-uses previously performed operations, which decreases the time taken to build and test your applications and libraries.

To amend the default cache settings, add the `cli.cache` object to your [Workspace Configuration](reference/configs/workspace-config). The object goes under `cli.cache` at the top level of the file, outside the `projects` sections.

```
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "cli": {
    "cache": {
      // ...
    },
  },
  "projects": {},
}
```

For more information, see [cache options](reference/configs/workspace-config#cache-options).

### [Cache environments](#cache-environments)

By default, disk cache is only enabled for local environments. The value of environment can be one of the following:

* `all` - allows disk cache on all machines.
* `local` - allows disk cache only on development machines.
* `ci` - allows disk cache only on continuous integration (CI) systems.

To change the environment setting to `all`, run the following command:

```
ng config cli.cache.environment all
```

For more information, see `environment` in [cache options](reference/configs/workspace-config#cache-options).

The Angular CLI checks for the presence and value of the `CI` environment variable to determine in which environment it is running.

### [Cache path](#cache-path)

By default, `.angular/cache` is used as a base directory to store cache results.

To change this path to `.cache/ng`, run the following command:

```
ng config cli.cache.path ".cache/ng"
```

### Sub-commands

This command has the following sub-commands

* [clean](cli/cache/clean)
* [disable](cli/cache/disable)
* [enable](cli/cache/enable)
* [info](cli/cache/info)

## [Options](#options)

`help`

Shows a help message for this command in the console.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/push-notifications
----

Push notifications are a compelling way to engage users. Through the power of service workers, notifications can be delivered to a device even when your application is not in focus.

The Angular service worker enables the display of push notifications and the handling of notification click events.

**HELPFUL:** When using the Angular service worker, push notification interactions are handled using the [`SwPush`](/api/service-worker/SwPush) service. To learn more about the browser APIs involved see [Push API](https://developer.mozilla.org/docs/Web/API/Push_API) and [Using the Notifications API](https://developer.mozilla.org/docs/Web/API/Notifications_API/Using_the_Notifications_API).

## [Notification payload](#notification-payload)

Invoke push notifications by pushing a message with a valid payload. See [`SwPush`](/api/service-worker/SwPush) for guidance.

**HELPFUL:** In Chrome, you can test push notifications without a backend. Open Devtools -> Application -> Service Workers and use the `Push` input to send a JSON notification payload.

## [Notification click handling](#notification-click-handling)

The default behavior for the `notificationclick` event is to close the notification and notify [`SwPush.notificationClicks`](/api/service-worker/SwPush#notificationClicks).

You can specify an additional operation to be executed on `notificationclick` by adding an `onActionClick` property to the `data` object, and providing a `default` entry. This is especially useful for when there are no open clients when a notification is clicked.

```
{
  "notification": {
    "title": "New Notification!",
    "data": {
      "onActionClick": {
        "default": {"operation": "openWindow", "url": "foo"}
      }
    }
  }
}
```

### [Operations](#operations)

The Angular service worker supports the following operations:

| Operations                  | Details                                                                                                                                          |
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
| `openWindow`                | Opens a new tab at the specified URL.                                                                                                            |
| `focusLastFocusedOrOpen`    | Focuses the last focused client. If there is no client open, then it opens a new tab at the specified URL.                                       |
| `navigateLastFocusedOrOpen` | Focuses the last focused client and navigates it to the specified URL. If there is no client open, then it opens a new tab at the specified URL. |
| `sendRequest`               | Send a simple GET request to the specified URL.                                                                                                  |

**IMPORTANT:** URLs are resolved relative to the service worker's registration scope.\
If an `onActionClick` item does not define a `url`, then the service worker's registration scope is used.

### [Actions](#actions)

Actions offer a way to customize how the user can interact with a notification.

Using the `actions` property, you can define a set of available actions. Each action is represented as an action button that the user can click to interact with the notification.

In addition, using the `onActionClick` property on the `data` object, you can tie each action to an operation to be performed when the corresponding action button is clicked:

```
{
  "notification": {
    "title": "New Notification!",
    "actions": [
      {"action": "foo", "title": "Open new tab"},
      {"action": "bar", "title": "Focus last"},
      {"action": "baz", "title": "Navigate last"},
      {"action": "qux", "title": "Send request in the background"},
      {"action": "other", "title": "Just notify existing clients"}
    ],
    "data": {
      "onActionClick": {
        "default": {"operation": "openWindow"},
        "foo": {"operation": "openWindow", "url": "/absolute/path"},
        "bar": {"operation": "focusLastFocusedOrOpen", "url": "relative/path"},
        "baz": {"operation": "navigateLastFocusedOrOpen", "url": "https://other.domain.com/"},
        "qux": {"operation": "sendRequest", "url": "https://yet.another.domain.com/"}
      }
    }
  }
}
```

**IMPORTANT:** If an action does not have a corresponding `onActionClick` entry, then the notification is closed and [`SwPush.notificationClicks`](/api/service-worker/SwPush#notificationClicks) is notified on existing clients.

## [More on Angular service workers](#more-on-angular-service-workers)

You might also be interested in the following:

----
url: https://angular.dev/guide/forms/typed-forms
----

As of Angular 14, reactive forms are strictly typed by default.

As background for this guide, you should already be familiar with [Angular Reactive Forms](guide/forms/reactive-forms).

## [Overview of Typed Forms](#overview-of-typed-forms)

[](https://www.youtube.com/watch?v=L-odCf4MfJc\&autoplay=1)

With Angular reactive forms, you explicitly specify a *form model*. As a simple example, consider this basic user login form:

```
const login = new FormGroup({
  email: new FormControl(''),
  password: new FormControl(''),
});
```

Angular provides many APIs for interacting with this [`FormGroup`](/api/forms/FormGroup). For example, you may call `login.value`, `login.controls`, `login.patchValue`, etc. (For a full API reference, see the [API documentation](api/forms/FormGroup).)

In previous Angular versions, most of these APIs included `any` somewhere in their types, and interacting with the structure of the controls, or the values themselves, was not type-safe. For example: you could write the following invalid code:

```
const emailDomain = login.value.email.domain;
```

With strictly typed reactive forms, the above code does not compile, because there is no `domain` property on `email`.

In addition to the added safety, the types enable a variety of other improvements, such as better autocomplete in IDEs and an explicit way to specify form structure.

These improvements currently apply only to *reactive* forms (not [*template-driven* forms](guide/forms/template-driven-forms)).

## [Untyped Forms](#untyped-forms)

Non-typed forms are still supported, and will continue to work as before. To use them, you must import the `Untyped` symbols from `@angular/forms`:

```
const login = new UntypedFormGroup({
  email: new UntypedFormControl(''),
  password: new UntypedFormControl(''),
});
```

Each `Untyped` symbol has exactly the same semantics as in previous Angular versions. By removing the `Untyped` prefixes, you can incrementally enable the types.

## [`FormControl`: Getting Started](#formcontrol-getting-started)

The simplest possible form consists of a single control:

```
const email = new FormControl('angularrox@gmail.com');
```

This control will be automatically inferred to have the type `FormControl<string|null>`. TypeScript will automatically enforce this type throughout the [`FormControl` API](api/forms/FormControl), such as `email.value`, `email.valueChanges`, and `email.setValue(...)`.

### [Nullability](#nullability)

You might wonder: why does the type of this control include `null`? This is because the control can become `null` at any time, by calling reset:

```
const email = new FormControl('angularrox@gmail.com');
email.reset();
console.log(email.value); // null
```

TypeScript will enforce that you always handle the possibility that the control has become `null`. If you want to make this control non-nullable, you may use the `nonNullable` option. This will cause the control to reset to its initial value, instead of `null`:

```
const email = new FormControl('angularrox@gmail.com', {nonNullable: true});
email.reset();
console.log(email.value); // angularrox@gmail.com
```

To reiterate, this option affects the runtime behavior of your form when `.reset()` is called, and should be flipped with care.

### [Specifying an Explicit Type](#specifying-an-explicit-type)

It is possible to specify the type, instead of relying on inference. Consider a control that is initialized to `null`. Because the initial value is `null`, TypeScript will infer `FormControl<null>`, which is narrower than we want.

```
const email = new FormControl(null);
email.setValue('angularrox@gmail.com'); // Error!
```

To prevent this, we explicitly specify the type as `string|null`:

```
const email = new FormControl<string | null>(null);
email.setValue('angularrox@gmail.com');
```

## [`FormArray`: Dynamic, Homogenous Collections](#formarray-dynamic-homogenous-collections)

A [`FormArray`](/api/forms/FormArray) contains an open-ended list of controls. The type parameter corresponds to the type of each inner control:

```
const names = new FormArray([new FormControl('Alex')]);
names.push(new FormControl('Jess'));
```

Pass an array of controls to `aliases.push()` when you need to add several entries at once.

```
const aliases = new FormArray([new FormControl('ng')]);
aliases.push([new FormControl('ngDev'), new FormControl('ngAwesome')]);
```

This [`FormArray`](/api/forms/FormArray) will have the inner controls type `FormControl<string|null>`.

If you want to have multiple different element types inside the array, you must use [`UntypedFormArray`](/api/forms/UntypedFormArray), because TypeScript cannot infer which element type will occur at which position.

A [`FormArray`](/api/forms/FormArray) also provides a `clear()` method to remove all controls it contains:

```
const aliases = new FormArray([new FormControl('ngDev'), new FormControl('ngAwesome')]);
aliases.clear();
console.log(aliases.length); // 0
```

## [`FormGroup` and `FormRecord`](#formgroup-and-formrecord)

Angular provides the [`FormGroup`](/api/forms/FormGroup) type for forms with an enumerated set of keys, and a type called [`FormRecord`](/api/forms/FormRecord), for open-ended or dynamic groups.

### [Partial Values](#partial-values)

Consider again a login form:

```
const login = new FormGroup({
  email: new FormControl('', {nonNullable: true}),
  password: new FormControl('', {nonNullable: true}),
});
```

On any [`FormGroup`](/api/forms/FormGroup), it is [possible to disable controls](api/forms/FormGroup). Any disabled control will not appear in the group's value.

As a consequence, the type of `login.value` is `Partial<{email: string, password: string}>`. The `Partial` in this type means that each member might be undefined.

More specifically, the type of `login.value.email` is `string|undefined`, and TypeScript will enforce that you handle the possibly `undefined` value (if you have `strictNullChecks` enabled).

If you want to access the value *including* disabled controls, and thus bypass possible `undefined` fields, you can use `login.getRawValue()`.

### [Optional Controls and Dynamic Groups](#optional-controls-and-dynamic-groups)

Some forms have controls that may or may not be present, which can be added and removed at runtime. You can represent these controls using *optional fields*:

```
interface LoginForm {
  email: FormControl<string>;
  password?: FormControl<string>;
}

const login = new FormGroup<LoginForm>({
  email: new FormControl('', {nonNullable: true}),
  password: new FormControl('', {nonNullable: true}),
});

login.removeControl('password');
```

In this form, we explicitly specify the type, which allows us to make the `password` control optional. TypeScript will enforce that only optional controls can be added or removed.

### [`FormRecord`](#formrecord)

Some [`FormGroup`](/api/forms/FormGroup) usages do not fit the above pattern because the keys are not known ahead of time. The [`FormRecord`](/api/forms/FormRecord) class is designed for that case:

```
const addresses = new FormRecord<FormControl<string | null>>({});
addresses.addControl('Andrew', new FormControl('2340 Folsom St'));
```

Any control of type `string|null` can be added to this [`FormRecord`](/api/forms/FormRecord).

If you need a [`FormGroup`](/api/forms/FormGroup) that is both dynamic (open-ended) and heterogeneous (the controls are different types), no improved type safety is possible, and you should use [`UntypedFormGroup`](/api/forms/UntypedFormGroup).

A [`FormRecord`](/api/forms/FormRecord) can also be built with the [`FormBuilder`](/api/forms/FormBuilder):

```
const addresses = fb.record({'Andrew': '2340 Folsom St'});
```

## [`FormBuilder` and `NonNullableFormBuilder`](#formbuilder-and-nonnullableformbuilder)

The [`FormBuilder`](/api/forms/FormBuilder) class has been upgraded to support the new types as well, in the same manner as the above examples.

Additionally, an additional builder is available: [`NonNullableFormBuilder`](/api/forms/NonNullableFormBuilder). This type is shorthand for specifying `{nonNullable: true}` on every control, and can eliminate significant boilerplate from large non-nullable forms. You can access it using the `nonNullable` property on a [`FormBuilder`](/api/forms/FormBuilder):

```
const fb = new FormBuilder();
const login = fb.nonNullable.group({
  email: '',
  password: '',
});
```

On the above example, both inner controls will be non-nullable (i.e. `nonNullable` will be set).

You can also inject it using the name [`NonNullableFormBuilder`](/api/forms/NonNullableFormBuilder).

----
url: https://angular.dev/guide/legacy-animations
----

Animation provides the illusion of motion: HTML elements change styles over time. Well-designed animations can make your application more intuitive and engaging, but they aren't just cosmetic. Animations can improve your application and the user experience in a number of ways:

* Without animations, web page transitions can seem abrupt and jarring
* Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
* Good animations intuitively call the user's attention to where it is needed

Typically, animations involve multiple style *transformations* over time. An HTML element can move, change color, grow or shrink, fade, or slide off the page. These changes can occur simultaneously or sequentially. You can control the timing of each transformation.

Angular's animation system is built on CSS functionality, which means you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1) page.

## [About this guide](#about-this-guide)

This guide covers the basic Angular animation features to get you started on adding Angular animations to your project.

## [Getting started](#getting-started)

The main Angular modules for animations are `@angular/animations` and `@angular/platform-browser`.

To get started with adding Angular animations to your project, import the animation-specific modules along with standard Angular functionality.

1. ### [Enabling the animations module](#enabling-the-animations-module)

   Import [`provideAnimationsAsync`](/api/platform-browser/animations/async/provideAnimationsAsync) from `@angular/platform-browser/animations/async` and add it to the providers list in the [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) function call.

   ### Enabling Animations

   ```
   1bootstrapApplication(AppComponent, {
   2  providers: [provideAnimationsAsync()],
   3});
   ```

   ### If you need immediate animations in your application

   If you need to have an animation happen immediately when your application is loaded, you will want to switch to the eagerly loaded animations module. Import [`provideAnimations`](/api/platform-browser/animations/provideAnimations) from `@angular/platform-browser/animations` instead, and use [`provideAnimations`](/api/platform-browser/animations/provideAnimations) **in place of** [`provideAnimationsAsync`](/api/platform-browser/animations/async/provideAnimationsAsync) in the [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) function call.

   For [`NgModule`](/api/core/NgModule) based applications import [`BrowserAnimationsModule`](/api/platform-browser/animations/BrowserAnimationsModule), which introduces the animation capabilities into your Angular root application module.

   ### app.module.ts

   ```
   import {NgModule} from '@angular/core';
   import {BrowserModule} from '@angular/platform-browser';
   import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

   @NgModule({
     imports: [BrowserModule, BrowserAnimationsModule],
     declarations: [],
     bootstrap: [],
   })
   export class AppModule {}
   ```

2. ### [Importing animation functions into component files](#importing-animation-functions-into-component-files)

   If you plan to use specific animation functions in component files, import those functions from `@angular/animations`.

   ### app.ts

   ```
   import {Component, HostBinding, inject} from '@angular/core';
   import {
     trigger,
     state,
     style,
     animate,
     transition,
     // ...
   } from '@angular/animations';
   ```

   See all [available animation functions](guide/legacy-animations#animations-api-summary) at the end of this guide.

3. ### [Adding the animation metadata property](#adding-the-animation-metadata-property)

   In the component file, add a metadata property called `animations:` within the [`@Component()`](/api/core/Component) decorator. You put the trigger that defines an animation within the `animations` metadata property.

   ### app.ts

   ```
   @Component({
     selector: 'app-root',
     templateUrl: 'app.html',
     styleUrls: ['app.css'],
     imports: [RouterLink, RouterOutlet],
     animations: [
       // animation triggers go here
     ],
   })
   ```

## [Animating a transition](#animating-a-transition)

Let's animate a transition that changes a single HTML element from one state to another. For example, you can specify that a button displays either **Open** or **Closed** based on the user's last action. When the button is in the `open` state, it's visible and yellow. When it's in the `closed` state, it's translucent and blue.

In HTML, these attributes are set using ordinary CSS styles such as color and opacity. In Angular, use the [`style()`](/api/animations/style) function to specify a set of CSS styles for use with animations. Collect a set of styles in an animation state, and give the state a name, such as `open` or `closed`.

**HELPFUL:** Let's create a new `open-close` component to animate with simple transitions.

Run the following command in terminal to generate the component:

```
ng g component open-close
```

This will create the component at `src/app/open-close.ts`.

### [Animation state and styles](#animation-state-and-styles)

Use Angular's [`state()`](api/animations/state) function to define different states to call at the end of each transition. This function takes two arguments: A unique name like `open` or `closed` and a [`style()`](/api/animations/style) function.

Use the [`style()`](/api/animations/style) function to define a set of styles to associate with a given state name. You must use *camelCase* for style attributes that contain dashes, such as `backgroundColor` or wrap them in quotes, such as `'background-color'`.

Let's see how Angular's [`state()`](api/animations/state) function works with the `style⁣­(⁠)` function to set CSS style attributes. In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a yellow background color.

### open-close.ts

```
// ...
state(
  'open',
  style({
    height: '200px',
    opacity: 1,
    backgroundColor: 'yellow',
  }),
),
```

In the following `closed` state, the button has a height of 100 pixels, an opacity of 0.8, and a background color of blue.

### open-close.ts

```
state(
  'closed',
  style({
    height: '100px',
    opacity: 0.8,
    backgroundColor: 'blue',
  }),
),
```

### [Transitions and timing](#transitions-and-timing)

In Angular, you can set multiple styles without any animation. However, without further refinement, the button instantly transforms with no fade, no shrinkage, or other visible indicator that a change is occurring.

To make the change less abrupt, you need to define an animation *transition* to specify the changes that occur between one state and another over a period of time. The [`transition()`](/api/animations/transition) function accepts two arguments: The first argument accepts an expression that defines the direction between two transition states, and the second argument accepts one or a series of [`animate()`](/api/animations/animate) steps.

Use the [`animate()`](/api/animations/animate) function to define the length, delay, and easing of a transition, and to designate the style function for defining styles while transitions are taking place. Use the [`animate()`](/api/animations/animate) function to define the [`keyframes()`](/api/animations/keyframes) function for multi-step animations. These definitions are placed in the second argument of the [`animate()`](/api/animations/animate) function.

#### [Animation metadata: duration, delay, and easing](#animation-metadata-duration-delay-and-easing)

The [`animate()`](/api/animations/animate) function (second argument of the transition function) accepts the `timings` and `styles` input parameters.

The `timings` parameter takes either a number or a string defined in three parts.

```
animate(duration);
```

or

```
animate('duration delay easing');
```

The first part, `duration`, is required. The duration can be expressed in milliseconds as a number without quotes, or in seconds with quotes and a time specifier. For example, a duration of a tenth of a second can be expressed as follows:

* As a plain number, in milliseconds: `100`

* In a string, as milliseconds: `'100ms'`

* In a string, as seconds: `'0.1s'`

The second argument, `delay`, has the same syntax as `duration`. For example:

* Wait for 100ms and then run for 200ms: `'0.2s 100ms'`

The third argument, `easing`, controls how the animation [accelerates and decelerates](https://easings.net) during its runtime. For example, `ease-in` causes the animation to begin slowly, and to pick up speed as it progresses.

* Wait for 100ms, run for 200ms. Use a deceleration curve to start out fast and slowly decelerate to a resting point: `'0.2s 100ms ease-out'`

* Run for 200ms, with no delay. Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end: `'0.2s ease-in-out'`

* Start immediately, run for 200ms. Use an acceleration curve to start slow and end at full velocity: `'0.2s ease-in'`

**HELPFUL:** See the Material Design website's topic on [Natural easing curves](https://material.io/design/motion/speed.html#easing) for general information on easing curves.

This example provides a state transition from `open` to `closed` with a 1-second transition between states.

### open-close.ts

```
transition('open => closed', [animate('1s')]),
```

In the preceding code snippet, the `=>` operator indicates unidirectional transitions, and `<=>` is bidirectional. Within the transition, [`animate()`](/api/animations/animate) specifies how long the transition takes. In this case, the state change from `open` to `closed` takes 1 second, expressed here as `1s`.

This example adds a state transition from the `closed` state to the `open` state with a 0.5-second transition animation arc.

### open-close.ts

```
transition('closed => open', [animate('0.5s')]),
```

**HELPFUL:** Some additional notes on using styles within [`state`](api/animations/state) and `transition` functions.

* Use [`state()`](api/animations/state) to define styles that are applied at the end of each transition, they persist after the animation completes
* Use [`transition()`](/api/animations/transition) to define intermediate styles, which create the illusion of motion during the animation
* When animations are disabled, [`transition()`](/api/animations/transition) styles can be skipped, but [`state()`](api/animations/state) styles can't
* Include multiple state pairs within the same [`transition()`](/api/animations/transition) argument:
  ```
  transition('on => off, off => void');
  ```

### [Triggering the animation](#triggering-the-animation)

An animation requires a *trigger*, so that it knows when to start. The [`trigger()`](/api/animations/trigger) function collects the states and transitions, and gives the animation a name, so that you can attach it to the triggering element in the HTML template.

The [`trigger()`](/api/animations/trigger) function describes the property name to watch for changes. When a change occurs, the trigger initiates the actions included in its definition. These actions can be transitions or other functions, as we'll see later on.

In this example, we'll name the trigger `openClose`, and attach it to the `button` element. The trigger describes the open and closed states, and the timings for the two transitions.

**HELPFUL:** Within each [`trigger()`](/api/animations/trigger) function call, an element can only be in one state at any given time. However, it's possible for multiple triggers to be active at once.

### [Defining animations and attaching them to the HTML template](#defining-animations-and-attaching-them-to-the-html-template)

Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the [`@Component()`](/api/core/Component) decorator.

### open-close.ts

```
@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      // ...
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      transition('open => closed', [animate('1s')]),
      transition('closed => open', [animate('0.5s')]),
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

}
```

When you've defined an animation trigger for a component, attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an `@` symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where `triggerName` is the name of the trigger, and `expression` evaluates to a defined animation state.

```
<div [@triggerName]="expression">…</div>
```

The animation is executed or triggered when the expression value changes to a new state.

The following code snippet binds the trigger to the value of the `isOpen` property.

### open-close.html

```
<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
```

In this example, when the `isOpen` expression evaluates to a defined state of `open` or `closed`, it notifies the trigger `openClose` of a state change. Then it's up to the `openClose` code to handle the state change and kick off a state change animation.

For elements entering or leaving a page (inserted or removed from the DOM), you can make the animations conditional. For example, use `*ngIf` with the animation trigger in the HTML template.

**HELPFUL:** In the component file, set the trigger that defines the animations as the value of the `animations:` property in the [`@Component()`](/api/core/Component) decorator.

In the HTML template file, use the trigger name to attach the defined animations to the HTML element to be animated.

### [Code review](#code-review)

Here are the code files discussed in the transition example.

### open-close.ts

```
@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      // ...
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.8,
          backgroundColor: 'blue',
        }),
      ),
      transition('open => closed', [animate('1s')]),
      transition('closed => open', [animate('0.5s')]),
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }

}
```

### open-close.html

```
<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
```

### open-close.css

```
:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  color: #000000;
  font-weight: bold;
  font-size: 20px;
}
```

### [Summary](#summary)

You learned to add animation to a transition between two states, using [`style()`](/api/animations/style) and [`state()`](api/animations/state) along with [`animate()`](/api/animations/animate) for the timing.

Learn about more advanced features in Angular animations under the Animation section, beginning with advanced techniques in [transition and triggers](guide/legacy-animations/transition-and-triggers).

## [Animations API summary](#animations-api-summary)

The functional API provided by the `@angular/animations` module provides a domain-specific language (DSL) for creating and controlling animations in Angular applications. See the [API reference](api?package=angular_animations\&status=8) for a complete listing and syntax details of the core functions and related data structures.

| Function name                                    | What it does                                                                                                                                                                                                                                                    |
| :----------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`trigger()`](/api/animations/trigger)           | Kicks off the animation and serves as a container for all other animation function calls. HTML template binds to `triggerName`. Use the first argument to declare a unique trigger name. Uses array syntax.                                                     |
| [`style()`](/api/animations/style)               | Defines one or more CSS styles to use in animations. Controls the visual appearance of HTML elements during animations. Uses object syntax.                                                                                                                     |
| [`state()`](api/animations/state)                | Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions.                                                                                  |
| [`animate()`](/api/animations/animate)           | Specifies the timing information for a transition. Optional values for `delay` and `easing`. Can contain [`style()`](/api/animations/style) calls within.                                                                                                       |
| [`transition()`](/api/animations/transition)     | Defines the animation sequence between two named states. Uses array syntax.                                                                                                                                                                                     |
| [`keyframes()`](/api/animations/keyframes)       | Allows a sequential change between styles within a specified time interval. Use within [`animate()`](/api/animations/animate). Can include multiple [`style()`](/api/animations/style) calls within each `keyframe()`. Uses array syntax.                       |
| [`group()`](api/animations/group)                | Specifies a group of animation steps (*inner animations*) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within [`sequence()`](/api/animations/sequence) or [`transition()`](/api/animations/transition). |
| [`query()`](/api/animations/query)               | Finds one or more inner HTML elements within the current element.                                                                                                                                                                                               |
| [`sequence()`](/api/animations/sequence)         | Specifies a list of animation steps that are run sequentially, one by one.                                                                                                                                                                                      |
| [`stagger()`](/api/animations/stagger)           | Staggers the starting time for animations for multiple elements.                                                                                                                                                                                                |
| [`animation()`](/api/animations/animation)       | Produces a reusable animation that can be invoked from elsewhere. Used together with [`useAnimation()`](/api/animations/useAnimation).                                                                                                                          |
| [`useAnimation()`](/api/animations/useAnimation) | Activates a reusable animation. Used with [`animation()`](/api/animations/animation).                                                                                                                                                                           |
| [`animateChild()`](/api/animations/animateChild) | Allows animations on child components to be run within the same timeframe as the parent.                                                                                                                                                                        |

## [More on Angular animations](#more-on-angular-animations)

**HELPFUL:** Check out this [presentation](https://www.youtube.com/watch?v=rnTK9meY5us), shown at the AngularConnect conference in November 2017, and the accompanying [source code](https://github.com/matsko/animationsftw.in).

You might also be interested in the following:

----
url: https://angular.dev/api/common/Time
----

Locale date getters are deprecated

## [API](#api)

```
    
      type Time = {  hours: number;  minutes: number;}
    
    
```

Jump to details

----
url: https://angular.dev/guide/ngmodules/overview
----

**IMPORTANT:** The Angular team recommends using [standalone components](guide/components) instead of [`NgModule`](/api/core/NgModule) for all new code. Use this guide to understand existing code built with [`@NgModule`](/api/core/NgModule).

An NgModule is a class marked by the [`@NgModule`](/api/core/NgModule) decorator. This decorator accepts *metadata* that tells Angular how to compile component templates and configure dependency injection.

```
import {NgModule} from '@angular/core';

@NgModule({
  // Metadata goes here
})
export class CustomMenuModule {}
```

An NgModule has two main responsibilities:

* Declaring components, directives, and pipes that belong to the NgModule
* Add providers to the injector for components, directives, and pipes that import the NgModule

## [Declarations](#declarations)

The `declarations` property of the [`@NgModule`](/api/core/NgModule) metadata declares the components, directives, and pipes that belong to the NgModule.

```
@NgModule({
  /* ... */
  // CustomMenu and CustomMenuItem are components.
  declarations: [CustomMenu, CustomMenuItem],
})
export class CustomMenuModule {}
```

In the example above, the components `CustomMenu` and `CustomMenuItem` belong to `CustomMenuModule`.

The `declarations` property additionally accepts *arrays* of components, directives, and pipes. These arrays, in turn, may also contain other arrays.

```
const MENU_COMPONENTS = [CustomMenu, CustomMenuItem];
const WIDGETS = [MENU_COMPONENTS, CustomSlider];

@NgModule({
  /* ... */
  // This NgModule declares all of CustomMenu, CustomMenuItem,
  // CustomSlider, and CustomCheckbox.
  declarations: [WIDGETS, CustomCheckbox],
})
export class CustomMenuModule {}
```

If Angular discovers any components, directives, or pipes declared in more than one NgModule, it reports an error.

Any components, directives, or pipes must be explicitly marked as `standalone: false` in order to be declared in an NgModule.

```
@Component({
  // Mark this component as `standalone: false` so that it can be declared in an NgModule.
  standalone: false,
  /* ... */
})
export class CustomMenu {
  /* ... */
}
```

### [imports](#imports)

Components declared in an NgModule may depend on other components, directives, and pipes. Add these dependencies to the `imports` property of the [`@NgModule`](/api/core/NgModule) metadata.

```
@NgModule({
  /* ... */
  // CustomMenu and CustomMenuItem depend on the PopupTrigger and SelectorIndicator components.
  imports: [PopupTrigger, SelectionIndicator],
  declarations: [CustomMenu, CustomMenuItem],
})
export class CustomMenuModule {}
```

The `imports` array accepts other NgModules, as well as standalone components, directives, and pipes.

### [exports](#exports)

An NgModule can *export* its declared components, directives, and pipes such that they're available to other components and NgModules.

```
@NgModule({
  imports: [PopupTrigger, SelectionIndicator],
  declarations: [CustomMenu, CustomMenuItem],

  // Make CustomMenu and CustomMenuItem available to
  // components and NgModules that import CustomMenuModule.
  exports: [CustomMenu, CustomMenuItem],
})
export class CustomMenuModule {}
```

The `exports` property is not limited to declarations, however. An NgModule can also export any other components, directives, pipes, and NgModules that it imports.

```
@NgModule({
  imports: [PopupTrigger, SelectionIndicator],
  declarations: [CustomMenu, CustomMenuItem],

  // Also make PopupTrigger available to any component or NgModule that imports CustomMenuModule.
  exports: [CustomMenu, CustomMenuItem, PopupTrigger],
})
export class CustomMenuModule {}
```

## [`NgModule` providers](#ngmodule-providers)

**TIP:** See the [Dependency Injection guide](guide/di) for information on dependency injection and providers.

An [`NgModule`](/api/core/NgModule) can specify `providers` for injected dependencies. These providers are available to:

* Any standalone component, directive, or pipe that imports the NgModule, and
* The `declarations` and `providers` of any *other* NgModule that imports the NgModule.

```
@NgModule({
  imports: [PopupTrigger, SelectionIndicator],
  declarations: [CustomMenu, CustomMenuItem],

  // Provide the OverlayManager service
  providers: [OverlayManager],
  /* ... */
})
export class CustomMenuModule {}

@NgModule({
  imports: [CustomMenuModule],
  declarations: [UserProfile],
  providers: [UserDataClient],
})
export class UserProfileModule {}
```

In the example above:

* The `CustomMenuModule` provides `OverlayManager`.
* The `CustomMenu` and `CustomMenuItem` components can inject `OverlayManager` because they're declared in `CustomMenuModule`.
* `UserProfile` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`.
* `UserDataClient` can inject `OverlayManager` because its NgModule imports `CustomMenuModule`.

### [The `forRoot` and `forChild` pattern](#the-forroot-and-forchild-pattern)

Some NgModules define a static `forRoot` method that accepts some configuration and returns an array of providers. The name "`forRoot`" is a convention that indicates that these providers are intended to be added exclusively to the *root* of your application during bootstrap.

Any providers included in this way are eagerly loaded, increasing the JavaScript bundle size of your initial page load.

```
bootstrapApplication(MyApplicationRoot, {
  providers: [CustomMenuModule.forRoot(/* some config */)],
});
```

Similarly, some NgModules may define a static `forChild` that indicates the providers are intended to be added to components within your application hierarchy.

```
@Component({
  /* ... */
  providers: [CustomMenuModule.forChild(/* some config */)],
})
export class UserProfile {
  /* ... */
}
```

## [Bootstrapping an application](#bootstrapping-an-application)

**IMPORTANT:** The Angular team recommends using [bootstrapApplication](api/platform-browser/bootstrapApplication) instead of `bootstrapModule` for all new code. Use this guide to understand existing applications bootstrapped with [`@NgModule`](/api/core/NgModule).

The [`@NgModule`](/api/core/NgModule) decorator accepts an optional `bootstrap` array that may contain one or more components.

You can use the [`bootstrapModule`](/api/core/PlatformRef#bootstrapModule) method from either [`platformBrowser`](api/platform-browser/platformBrowser) or [`platformServer`](api/platform-server/platformServer) to start an Angular application. When run, this function locates any elements on the page with a CSS selector that matches the listed component(s) and renders those components on the page.

```
import {platformBrowser} from '@angular/platform-browser';

@NgModule({
  bootstrap: [MyApplication],
})
export class MyApplicationModule {}

platformBrowser().bootstrapModule(MyApplicationModule);
```

Components listed in `bootstrap` are automatically included in the NgModule's declarations.

When you bootstrap an application from an NgModule, the collected `providers` of this module and all of the `providers` of its `imports` are eagerly loaded and available to inject for the entire application.

----
url: https://angular.dev/guide/http/making-requests
----

[`HttpClient`](/api/common/http/HttpClient) has methods corresponding to the different HTTP verbs used to make requests, both to load data and to apply mutations on the server. Each method returns an [RxJS `Observable`](https://rxjs.dev/guide/observable) which, when subscribed, sends the request and then emits the results when the server responds.

**NOTE:** `Observable`s created by [`HttpClient`](/api/common/http/HttpClient) may be subscribed any number of times and will make a new backend request for each subscription.

Through an options object passed to the request method, various properties of the request and the returned response type can be adjusted.

## [Fetching JSON data](/guide/http/making-requests#fetching-json-data)

Fetching data from a backend often requires making a GET request using the [`HttpClient.get()`](api/common/http/HttpClient#get) method. This method takes two arguments: the string endpoint URL from which to fetch, and an *optional options* object to configure the request.

For example, to fetch configuration data from a hypothetical API using the [`HttpClient.get()`](/api/common/http/HttpClient#get) method:

```
http.get<Config>('/api/config').subscribe((config) => {
  // process the configuration.
});
```

Note the generic type argument which specifies that the data returned by the server will be of type `Config`. This argument is optional, and if you omit it, the returned data will have type `Object`.

**TIP:** When dealing with data of uncertain structure and potential `undefined` or `null` values, consider using the `unknown` type instead of `Object` as the response type.

**CRITICAL:** The generic type of request methods is a type **assertion** about the data returned by the server. [`HttpClient`](/api/common/http/HttpClient) does not verify that the actual return data matches this type.

## [Fetching other types of data](/guide/http/making-requests#fetching-other-types-of-data)

By default, [`HttpClient`](/api/common/http/HttpClient) assumes that servers will return JSON data. When interacting with a non-JSON API, you can tell [`HttpClient`](/api/common/http/HttpClient) what response type to expect when making the request. This is done with the `responseType` option.

| **`responseType` value** | **Returned response type**                                                                                                                |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `'json'` (default)       | JSON data of the given generic type                                                                                                       |
| `'text'`                 | string data                                                                                                                               |
| `'arraybuffer'`          | [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) containing the raw response bytes |
| `'blob'`                 | [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob) instance                                                                        |

For example, you can ask [`HttpClient`](/api/common/http/HttpClient) to download the raw bytes of a `.jpeg` image into an `ArrayBuffer`:

```
http.get('/images/dog.jpg', {responseType: 'arraybuffer'}).subscribe((buffer) => {
  console.log('The image is ' + buffer.byteLength + ' bytes large');
});
```

### Literal value for `responseType`

Because the value of `responseType` affects the type returned by [`HttpClient`](/api/common/http/HttpClient), it must have a literal type and not a `string` type.

This happens automatically if the options object passed to the request method is a literal object, but if you're extracting the request options out into a variable or helper method you might need to explicitly specify it as a literal, such as `responseType: 'text' as const`.

## [Mutating server state](/guide/http/making-requests#mutating-server-state)

Server APIs that perform mutations often require making POST requests with a request body specifying the new state or the change to be made.

The [`HttpClient.post()`](api/common/http/HttpClient#post) method behaves similarly to `get()`, and accepts an additional `body` argument before its options:

```
http.post<Config>('/api/config', newConfig).subscribe((config) => {
  console.log('Updated config:', config);
});
```

Many different types of values can be provided as the request's `body`, and [`HttpClient`](/api/common/http/HttpClient) will serialize them accordingly:

| **`body` type**                                                                                                               | **Serialized as**                                    |
| ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| string                                                                                                                        | Plain text                                           |
| number, boolean, array, or plain object                                                                                       | JSON                                                 |
| [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)                       | raw data from the buffer                             |
| [`Blob`](https://developer.mozilla.org/docs/Web/API/Blob)                                                                     | raw data with the `Blob`'s content type              |
| [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData)                                                             | `multipart/form-data` encoded data                   |
| [`HttpParams`](api/common/http/HttpParams) or [`URLSearchParams`](https://developer.mozilla.org/docs/Web/API/URLSearchParams) | `application/x-www-form-urlencoded` formatted string |

**IMPORTANT:** Remember to `.subscribe()` to mutation request `Observable`s in order to actually fire the request.

## [Setting URL parameters](/guide/http/making-requests#setting-url-parameters)

Specify request parameters that should be included in the request URL using the `params` option.

Passing an object literal is the simplest way of configuring URL parameters:

```
http
  .get('/api/config', {
    params: {filter: 'all'},
  })
  .subscribe((config) => {
    // ...
  });
```

Alternatively, pass an instance of [`HttpParams`](/api/common/http/HttpParams) if you need more control over the construction or serialization of the parameters.

**IMPORTANT:** Instances of [`HttpParams`](/api/common/http/HttpParams) are *immutable* and cannot be directly changed. Instead, mutation methods such as `append()` return a new instance of [`HttpParams`](/api/common/http/HttpParams) with the mutation applied.

```
const baseParams = new HttpParams().set('filter', 'all');

http
  .get('/api/config', {
    params: baseParams.set('details', 'enabled'),
  })
  .subscribe((config) => {
    // ...
  });
```

You can instantiate [`HttpParams`](/api/common/http/HttpParams) with a custom [`HttpParameterCodec`](/api/common/http/HttpParameterCodec) that determines how [`HttpClient`](/api/common/http/HttpClient) will encode the parameters into the URL.

### [Custom parameter encoding](/guide/http/making-requests#custom-parameter-encoding)

By default, [`HttpParams`](/api/common/http/HttpParams) uses the built-in [`HttpUrlEncodingCodec`](api/common/http/HttpUrlEncodingCodec) to encode and decode parameter keys and values.

You can provide your own implementation of [`HttpParameterCodec`](api/common/http/HttpParameterCodec) to customize how encoding and decoding are applied.

```
import {HttpClient, HttpParams, HttpParameterCodec} from '@angular/common/http';
import {inject} from '@angular/core';

export class CustomHttpParamEncoder implements HttpParameterCodec {
  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }

  encodeValue(value: string): string {
    return encodeURIComponent(value);
  }

  decodeKey(key: string): string {
    return decodeURIComponent(key);
  }

  decodeValue(value: string): string {
    return decodeURIComponent(value);
  }
}

export class ApiService {
  private http = inject(HttpClient);

  search() {
    const params = new HttpParams({
      encoder: new CustomHttpParamEncoder(),
    })
      .set('email', 'dev+alerts@example.com')
      .set('q', 'a & b? c/d = e');

    return this.http.get('/api/items', {params});
  }
}
```

## [Setting request headers](/guide/http/making-requests#setting-request-headers)

Specify request headers that should be included in the request using the `headers` option.

Passing an object literal is the simplest way of configuring request headers:

```
http
  .get('/api/config', {
    headers: {
      'X-Debug-Level': 'verbose',
    },
  })
  .subscribe((config) => {
    // ...
  });
```

Alternatively, pass an instance of [`HttpHeaders`](/api/common/http/HttpHeaders) if you need more control over the construction of headers.

**IMPORTANT:** Instances of [`HttpHeaders`](/api/common/http/HttpHeaders) are *immutable* and cannot be directly changed. Instead, mutation methods such as `append()` return a new instance of [`HttpHeaders`](/api/common/http/HttpHeaders) with the mutation applied.

```
const baseHeaders = new HttpHeaders().set('X-Debug-Level', 'minimal');

http
  .get<Config>('/api/config', {
    headers: baseHeaders.set('X-Debug-Level', 'verbose'),
  })
  .subscribe((config) => {
    // ...
  });
```

## [Interacting with the server response events](/guide/http/making-requests#interacting-with-the-server-response-events)

For convenience, [`HttpClient`](/api/common/http/HttpClient) by default returns an `Observable` of the data returned by the server (the response body). Occasionally it's desirable to examine the actual response, for example to retrieve specific response headers.

To access the entire response, set the `observe` option to `'response'`:

```
http.get<Config>('/api/config', {observe: 'response'}).subscribe((res) => {
  console.log('Response status:', res.status);
  console.log('Body:', res.body);
});
```

### Literal value for `observe`

Because the value of `observe` affects the type returned by [`HttpClient`](/api/common/http/HttpClient), it must have a literal type and not a `string` type.

This happens automatically if the options object passed to the request method is a literal object, but if you're extracting the request options out into a variable or helper method you might need to explicitly specify it as a literal, such as `observe: 'response' as const`.

## [Receiving raw progress events](/guide/http/making-requests#receiving-raw-progress-events)

In addition to the response body or response object, [`HttpClient`](/api/common/http/HttpClient) can also return a stream of raw *events* corresponding to specific moments in the request lifecycle. These events include when the request is sent, when the response header is returned, and when the body is complete. These events can also include *progress events* that report upload and download status for large request or response bodies.

Progress events are disabled by default (as they have a performance cost) but can be enabled with the `reportUploadProgress` and `reportDownloadProgress` options.

**NOTE:** The default fetch backend of [`HttpClient`](/api/common/http/HttpClient) does not support *upload* progress events and throws an error if you set `reportUploadProgress`. If your app needs upload progress events, configure [`HttpClient`](/api/common/http/HttpClient) with [`withXhr()`](/api/common/http/withXhr) in `provideHttpClient(...)`.

To observe the event stream, set the `observe` option to `'events'`:

```
http
  .get('/api/download', {
    reportDownloadProgress: true,
    observe: 'events',
  })
  .subscribe((event) => {
    switch (event.type) {
      case HttpEventType.DownloadProgress:
        console.log('Downloaded ' + event.loaded + ' out of ' + event.total + ' bytes');
        break;
      case HttpEventType.Response:
        console.log('Finished downloading!');
        break;
    }
  });
```

### Literal value for `observe`

Because the value of `observe` affects the type returned by [`HttpClient`](/api/common/http/HttpClient), it must have a literal type and not a `string` type.

This happens automatically if the options object passed to the request method is a literal object, but if you're extracting the request options out into a variable or helper method you might need to explicitly specify it as a literal, such as `observe: 'events' as const`.

Each [`HttpEvent`](/api/common/http/HttpEvent) reported in the event stream has a `type` which distinguishes what the event represents:

| **`type` value**                                                                    | **Event meaning**                                                                                                                |
| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| [`HttpEventType.Sent`](/api/common/http/HttpEventType#Sent)                         | The request has been dispatched to the server                                                                                    |
| [`HttpEventType.UploadProgress`](/api/common/http/HttpEventType#UploadProgress)     | An [`HttpUploadProgressEvent`](/api/common/http/HttpUploadProgressEvent) reporting progress on uploading the request body        |
| [`HttpEventType.ResponseHeader`](/api/common/http/HttpEventType#ResponseHeader)     | The head of the response has been received, including status and headers                                                         |
| [`HttpEventType.DownloadProgress`](/api/common/http/HttpEventType#DownloadProgress) | An [`HttpDownloadProgressEvent`](/api/common/http/HttpDownloadProgressEvent) reporting progress on downloading the response body |
| [`HttpEventType.Response`](/api/common/http/HttpEventType#Response)                 | The entire response has been received, including the response body                                                               |
| [`HttpEventType.User`](/api/common/http/HttpEventType#User)                         | A custom event from an HTTP interceptor.                                                                                         |

## [Handling request failure](/guide/http/making-requests#handling-request-failure)

There are three ways an HTTP request can fail:

* A network or connection error can prevent the request from reaching the backend server.
* A request didn't respond in time when the timeout option was set.
* The backend can receive the request but fail to process it, and return an error response.

[`HttpClient`](/api/common/http/HttpClient) captures all of the above kinds of errors in an [`HttpErrorResponse`](/api/common/http/HttpErrorResponse) which it returns through the `Observable`'s error channel. Network and timeout errors have a `status` code of `0`. For timeouts, the `error` is a `DOMException` named `TimeoutError`; for network errors, it is the error thrown by `fetch` (or a [`ProgressEvent`](https://developer.mozilla.org/docs/Web/API/ProgressEvent) when using [`withXhr()`](/api/common/http/withXhr)). Backend errors have the failing `status` code returned by the backend, and the error response as the `error`. Inspect the response to identify the error's cause and the appropriate action to handle the error.

The [RxJS library](https://rxjs.dev/) offers several operators which can be useful for error handling.

You can use the `catchError` operator to transform an error response into a value for the UI. This value can tell the UI to display an error page or value, and capture the error's cause if necessary.

Sometimes transient errors such as network interruptions can cause a request to fail unexpectedly, and simply retrying the request will allow it to succeed. RxJS provides several *retry* operators which automatically re-subscribe to a failed `Observable` under certain conditions. For example, the `retry()` operator will automatically attempt to re-subscribe a specified number of times.

### [Timeouts](/guide/http/making-requests#timeouts)

To set a timeout for a request, you can set the `timeout` option to a number of milliseconds along with other request options. If the backend request does not complete within the specified time, the request will be aborted and an error will be emitted.

**NOTE:** The timeout will only apply to the backend HTTP request itself. It is not a timeout for the entire request handling chain. Therefore, this option is not affected by any delay introduced by interceptors.

```
http
  .get('/api/config', {
    timeout: 3000,
  })
  .subscribe({
    next: (config) => {
      console.log('Config fetched successfully:', config);
    },
    error: (err) => {
      // If the request times out, an error will have been emitted.
    },
  });
```

## [Advanced fetch options](/guide/http/making-requests#advanced-fetch-options)

Angular's [`HttpClient`](/api/common/http/HttpClient) supports advanced fetch API options that can improve performance and user experience. These options are available when using the fetch backend, which is the default.

### [Fetch options](/guide/http/making-requests#fetch-options)

The following options provide fine-grained control over request behavior when using the fetch backend.

#### [Keep-alive connections](/guide/http/making-requests#keep-alive-connections)

The `keepalive` option allows a request to outlive the page that initiated it. This is particularly useful for analytics or logging requests that need to complete even if the user navigates away from the page.

```
http
  .post('/api/analytics', analyticsData, {
    keepalive: true,
  })
  .subscribe();
```

#### [HTTP caching control](/guide/http/making-requests#http-caching-control)

The `cache` option controls how the request interacts with the browser's HTTP cache, which can significantly improve performance for repeated requests.

```
//  Use cached response regardless of freshness
http
  .get('/api/config', {
    cache: 'force-cache',
  })
  .subscribe((config) => {
    // ...
  });

// Always fetch from network, bypass cache
http
  .get('/api/live-data', {
    cache: 'no-cache',
  })
  .subscribe((data) => {
    // ...
  });

// Use cached response only, fail if not in cache
http
  .get('/api/static-data', {
    cache: 'only-if-cached',
  })
  .subscribe((data) => {
    // ...
  });
```

#### [Request priority for Core Web Vitals](/guide/http/making-requests#request-priority-for-core-web-vitals)

The `priority` option allows you to indicate the relative importance of a request, helping browsers optimize resource loading for better Core Web Vitals scores.

```
// High priority for critical resources
http
  .get('/api/user-profile', {
    priority: 'high',
  })
  .subscribe((profile) => {
    // ...
  });

// Low priority for non-critical resources
http
  .get('/api/recommendations', {
    priority: 'low',
  })
  .subscribe((recommendations) => {
    // ...
  });

// Auto priority (default) lets the browser decide
http
  .get('/api/settings', {
    priority: 'auto',
  })
  .subscribe((settings) => {
    // ...
  });
```

Available `priority` values:

* `'high'`: High priority, loaded early (e.g., critical user data, above-the-fold content)
* `'low'`: Low priority, loaded when resources are available (e.g., analytics, prefetch data)
* `'auto'`: Browser determines priority based on request context (default)

**TIP:** Use `priority: 'high'` for requests that affect Largest Contentful Paint (LCP) and `priority: 'low'` for requests that don't impact initial user experience.

#### [Request mode](/guide/http/making-requests#request-mode)

The `mode` option controls how the request handles cross-origin requests and determines the response type.

```
// Same-origin requests only
http
  .get('/api/local-data', {
    mode: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// CORS-enabled cross-origin requests
http
  .get('https://api.external.com/data', {
    mode: 'cors',
  })
  .subscribe((data) => {
    // ...
  });

// No-CORS mode for simple cross-origin requests
http
  .get('https://external-api.com/public-data', {
    mode: 'no-cors',
  })
  .subscribe((data) => {
    // ...
  });
```

Available `mode` values:

* `'same-origin'`: Only allow same-origin requests, fail for cross-origin requests
* `'cors'`: Allow cross-origin requests with CORS (default)
* `'no-cors'`: Allow simple cross-origin requests without CORS, response is opaque

**TIP:** In the browser, use `mode: 'same-origin'` for sensitive requests that should never go cross-origin.

**IMPORTANT:** During SSR on Node.js, [`HttpClient`](/api/common/http/HttpClient) uses Node.js's [Undici-based Fetch implementation](https://nodejs.org/api/globals.html#fetch). [Undici does not enforce browser CORS checks](https://undici.nodejs.org/#cors), so `mode: 'same-origin'` does not restrict server-side requests. Validate user-influenced URLs against an allowlist.

#### [Redirect handling](/guide/http/making-requests#redirect-handling)

The `redirect` option specifies how to handle redirect responses from the server.

```
// Follow redirects automatically (default behavior)
http
  .get('/api/resource', {
    redirect: 'follow',
  })
  .subscribe((data) => {
    // ...
  });

// Prevent automatic redirects
http
  .get('/api/resource', {
    redirect: 'manual',
  })
  .subscribe((response) => {
    // Handle redirect manually
  });

// Treat redirects as errors
http
  .get('/api/resource', {
    redirect: 'error',
  })
  .subscribe({
    next: (data) => {
      // Success response
    },
    error: (err) => {
      // Redirect responses will trigger this error handler
    },
  });
```

Available `redirect` values:

* `'follow'`: Automatically follow redirects (default)
* `'error'`: Treat redirects as errors
* `'manual'`: Don't follow redirects automatically, return redirect response

**TIP:** Use `redirect: 'manual'` when you need to handle redirects with custom logic.

#### [Credentials handling](/guide/http/making-requests#credentials-handling)

The `credentials` option controls whether cookies, authorization headers, and other credentials are sent with cross-origin requests. This is particularly important for authentication scenarios.

```
// Include credentials for cross-origin requests
http
  .get('https://api.example.com/protected-data', {
    credentials: 'include',
  })
  .subscribe((data) => {
    // ...
  });

// Never send credentials (default for cross-origin)
http
  .get('https://api.example.com/public-data', {
    credentials: 'omit',
  })
  .subscribe((data) => {
    // ...
  });

// Send credentials only for same-origin requests
http
  .get('/api/user-data', {
    credentials: 'same-origin',
  })
  .subscribe((data) => {
    // ...
  });

// withCredentials overrides credentials setting
http
  .get('https://api.example.com/data', {
    credentials: 'omit', // This will be ignored
    withCredentials: true, // This forces credentials: 'include'
  })
  .subscribe((data) => {
    // Request will include credentials despite credentials: 'omit'
  });

// Legacy approach (still supported)
http
  .get('https://api.example.com/data', {
    withCredentials: true,
  })
  .subscribe((data) => {
    // Equivalent to credentials: 'include'
  });
```

**IMPORTANT:** The `withCredentials` option takes precedence over the `credentials` option. If both are specified, `withCredentials: true` will always result in `credentials: 'include'`, regardless of the explicit `credentials` value.

Available `credentials` values:

* `'omit'`: Never send credentials
* `'same-origin'`: Send credentials only for same-origin requests (default)
* `'include'`: Always send credentials, even for cross-origin requests

**TIP:** Use `credentials: 'include'` when you need to send authentication cookies or headers to a different domain that supports CORS. Avoid mixing `credentials` and `withCredentials` options to prevent confusion.

**IMPORTANT:** During SSR on Node.js, `credentials: 'include'` does not automatically forward cookies from the incoming browser request. The `credentials` option does not remove `Cookie` or `Authorization` headers that you add explicitly. [Undici permits some headers that browsers forbid](https://undici.nodejs.org/#forbidden-and-safelisted-header-names), so only forward credential headers to trusted origins.

#### [Referrer](/guide/http/making-requests#referrer)

The `referrer` option allows you to control what referrer information is sent with the request. This is important for privacy and security considerations.

```
// Send a specific referrer URL
http
  .get('/api/data', {
    referrer: 'https://example.com/page',
  })
  .subscribe((data) => {
    // ...
  });

// Use the current page as referrer (default behavior)
http
  .get('/api/analytics', {
    referrer: 'about:client',
  })
  .subscribe((data) => {
    // ...
  });
```

The `referrer` option accepts:

* A valid URL string: Sets the specific referrer URL to send
* An empty string `''`: Sends no referrer information
* `'about:client'`: Uses the default referrer (current page URL)

**TIP:** Use `referrer: ''` for sensitive requests where you don't want to leak the referring page URL.

#### [Referrer policy](/guide/http/making-requests#referrer-policy)

The `referrerPolicy` option controls how much referrer information—the URL of the page making the request—is sent along with an HTTP request. This setting affects both privacy and analytics, allowing you to balance data visibility with security considerations.

```
// Send no referrer information regardless of the current page
http
  .get('/api/data', {
    referrerPolicy: 'no-referrer',
  })
  .subscribe();

// Send origin only (e.g. https://example.com)
http
  .get('/api/analytics', {
    referrerPolicy: 'origin',
  })
  .subscribe();
```

The `referrerPolicy` option accepts:

* `'no-referrer'` Never send the `Referer` header.
* `'no-referrer-when-downgrade'` Sends the referrer for same-origin and secure (HTTPS→HTTPS) requests, but omits it when navigating from a secure to a less secure origin (HTTPS→HTTP).
* `'origin'` Sends only the origin (scheme, host, port) of the referrer, omitting path and query information.
* `'origin-when-cross-origin'` Sends the full URL for same-origin requests, but only the origin for cross-origin requests.
* `'same-origin'` Sends the full URL for same-origin requests and no referrer for cross-origin requests.
* `'strict-origin'` Sends only the origin, and only if the protocol security level is not downgraded (e.g., HTTPS→HTTPS). Omits the referrer on downgrade.
* `'strict-origin-when-cross-origin'` Default browser behavior. Sends the full URL for same-origin requests, the origin for cross-origin requests when not downgraded, and omits the referrer on downgrade.
* `'unsafe-url'` Always sends the full URL (including path and query). This can expose sensitive data and should be used with caution.

**TIP:** Prefer conservative values such as `'no-referrer'`, `'origin'`, or `'strict-origin-when-cross-origin'` for privacy-sensitive requests.

#### [Integrity](/guide/http/making-requests#integrity)

The `integrity` option allows you to verify that the response hasn't been tampered with by providing a cryptographic hash of the expected content. This is particularly useful for loading scripts or other resources from CDNs.

```
// Verify response integrity with SHA-256 hash
http
  .get('/api/script.js', {
    integrity: 'sha256-ABC123...',
    responseType: 'text',
  })
  .subscribe((script) => {
    // Script content is verified against the hash
  });
```

**IMPORTANT:** The `integrity` option requires an exact match between the response content and the provided hash. If the content doesn't match, the request will fail with a network error.

**CRITICAL:** During SSR, the Fetch implementation reads the entire response body to verify `integrity` before returning a response, as required by the [Fetch Standard](https://fetch.spec.whatwg.org/#concept-main-fetch). Angular enforces [`maxResponseBodySize`](/guide/ssr#configuring-the-response-body-size-limit) only after Fetch returns a response, so this limit does not constrain the data buffered during integrity verification.

**TIP:** Use subresource integrity when loading critical resources from external sources to ensure they haven't been modified. Generate hashes using tools like `openssl`.

## [HTTP `Observable`s](/guide/http/making-requests#http-observables)

Each request method on [`HttpClient`](/api/common/http/HttpClient) constructs and returns an `Observable` of the requested response type. Understanding how these `Observable`s work is important when using [`HttpClient`](/api/common/http/HttpClient).

[`HttpClient`](/api/common/http/HttpClient) produces what RxJS calls "cold" `Observable`s, meaning that no actual request happens until the `Observable` is subscribed. Only then is the request actually dispatched to the server. Subscribing to the same `Observable` multiple times will trigger multiple backend requests. Each subscription is independent.

**TIP:** You can think of [`HttpClient`](/api/common/http/HttpClient) `Observable`s as *blueprints* for actual server requests.

Once subscribed, unsubscribing will abort the in-progress request. This is very useful if the `Observable` is subscribed via the `async` pipe, as it will automatically cancel the request if the user navigates away from the current page. Additionally, if you use the `Observable` with an RxJS combinator like `switchMap`, this cancellation will clean up any stale requests.

Once the response returns, `Observable`s from [`HttpClient`](/api/common/http/HttpClient) usually complete (although interceptors can influence this).

Because of the automatic completion, there is usually no risk of memory leaks if [`HttpClient`](/api/common/http/HttpClient) subscriptions are not cleaned up. However, as with any async operation, we strongly recommend that you clean up subscriptions when the component using them is destroyed, as the subscription callback may otherwise run and encounter errors when it attempts to interact with the destroyed component.

**TIP:** Using the `async` pipe or the [`toSignal`](/api/core/rxjs-interop/toSignal) operation to subscribe to `Observable`s ensures that subscriptions are disposed properly.

## [Best practices](/guide/http/making-requests#best-practices)

While [`HttpClient`](/api/common/http/HttpClient) can be injected and used directly from components, generally we recommend you create reusable, injectable services which isolate and encapsulate data access logic. For example, this `UserService` encapsulates the logic to request data for a user by their id:

```
@Service()
export class UserService {
  private http = inject(HttpClient);

  getUser(id: string): Observable<User> {
    return this.http.get<User>(`/api/user/${id}`);
  }
}
```

Within a component, you can combine `@if` with the `async` pipe to render the UI for the data only after it's finished loading:

```
import {AsyncPipe} from '@angular/common';

@Component({
  imports: [AsyncPipe],
  template: `
    @if (user$ | async; as user) {
      <p>Name: {{ user.name }}</p>
      <p>Biography: {{ user.biography }}</p>
    }
  `,
})
export class UserProfile {
  userId = input.required<string>();
  user$!: Observable<User>;

  private userService = inject(UserService);

  constructor() {
    effect(() => {
      this.user$ = this.userService.getUser(this.userId());
    });
  }
}
```

----
url: https://angular.dev/tools/cli/environments
----

You can define different named build configurations for your project, such as `development` and `staging`, with different defaults.

Each named configuration can have defaults for any of the options that apply to the various builder targets, such as `build`, `serve`, and `test`. The [Angular CLI](tools/cli) `build`, `serve`, and `test` commands can then replace files with appropriate versions for your intended target environment.

## [Angular CLI configurations](/tools/cli/environments#angular-cli-configurations)

Angular CLI builders support a `configurations` object, which allows overwriting specific options for a builder based on the configuration provided on the command line.

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            // By default, disable source map generation.
            "sourceMap": false
          },
          "configurations": {
            // For the `debug` configuration, enable source maps.
            "debug": {
              "sourceMap": true
            }
          }
        },
        …
      }
    }
  }
}
```

You can choose which configuration to use with the `--configuration` option.

```
ng build --configuration debug
```

Configurations can be applied to any Angular CLI builder. Multiple configurations can be specified with a comma separator. The configurations are applied in order, with conflicting options using the value from the last configuration.

```
ng build --configuration debug,production,customer-facing
```

## [Configure environment-specific defaults](/tools/cli/environments#configure-environment-specific-defaults)

`@angular/build:application` supports file replacements, an option for substituting source files before executing a build. Using this in combination with `--configuration` provides a mechanism for configuring environment-specific data in your application.

Start by [generating environments](cli/generate/environments) to create the `src/environments/` directory and configure the project to use file replacements.

```
ng generate environments
```

The project's `src/environments/` directory contains the base configuration file, `environment.ts`, which provides the default configuration for production. You can override default values for additional environments, such as `development` and `staging`, in target-specific configuration files.

For example:

```
my-app/src/environments
├── environment.development.ts
├── environment.staging.ts
└── environment.ts
```

The base file `environment.ts`, contains the default environment settings. For example:

```
export const environment = {
  production: true,
};
```

The `build` command uses this as the build target when no environment is specified. You can add further variables, either as additional properties on the environment object, or as separate objects. For example, the following adds a default for a variable to the default environment:

```
export const environment = {
  production: true,
  apiUrl: 'http://my-prod-url',
};
```

**CRITICAL:** Files in `src/environments/` are bundled into your client-side application and visible to anyone who loads the page. Never store secrets such as API keys here. Use a server-side proxy or a secrets manager instead.

You can add target-specific configuration files, such as `environment.development.ts`. The following content sets default values for the development build target:

```
export const environment = {
  production: false,
  apiUrl: 'http://my-dev-url',
};
```

## [Using environment-specific variables in your app](/tools/cli/environments#using-environment-specific-variables-in-your-app)

To use the environment configurations you have defined, your components must import the original environments file:

```
import {environment} from './environments/environment';
```

This ensures that the build and serve commands can find the configurations for specific build targets.

The following code in the component file (`app.ts`) uses an environment variable defined in the configuration files.

```
import {environment} from './../environments/environment';

// Fetches from `http://my-prod-url` in production, `http://my-dev-url` in development.
fetch(environment.apiUrl);
```

The main CLI configuration file, `angular.json`, contains a `fileReplacements` section in the configuration for each build target, which lets you replace any file in the TypeScript program with a target-specific version of that file. This is useful for including target-specific code or variables in a build that targets a specific environment, such as production or staging.

By default no files are replaced, however `ng generate environments` sets up this configuration automatically. You can change or add file replacements for specific build targets by editing the `angular.json` configuration directly.

```
"configurations": {
  "development": {
    "fileReplacements": [
        {
          "replace": "src/environments/environment.ts",
          "with": "src/environments/environment.development.ts"
        }
      ],
      …
```

This means that when you build your development configuration with `ng build --configuration development`, the `src/environments/environment.ts` file is replaced with the target-specific version of the file, `src/environments/environment.development.ts`.

To add a staging environment, create a copy of `src/environments/environment.ts` called `src/environments/environment.staging.ts`, then add a `staging` configuration to `angular.json`:

```
"configurations": {
  "development": { … },
  "production": { … },
  "staging": {
    "fileReplacements": [
      {
        "replace": "src/environments/environment.ts",
        "with": "src/environments/environment.staging.ts"
      }
    ]
  }
}
```

You can add more configuration options to this target environment as well. Any option that your build supports can be overridden in a build target configuration.

To build using the staging configuration, run the following command:

```
ng build --configuration staging
```

By default, the `build` target includes `production` and `development` configurations and `ng serve` uses the development build of the application. You can also configure `ng serve` to use the targeted build configuration if you set the `buildTarget` option:

```
"serve": {
  "builder": "@angular/build:dev-server",
  "options": { … },
  "configurations": {
    "development": {
      // Use the `development` configuration of the `build` target.
      "buildTarget": "my-app:build:development"
    },
    "production": {
      // Use the `production` configuration of the `build` target.
      "buildTarget": "my-app:build:production"
    }
  },
  "defaultConfiguration": "development"
},
```

The `defaultConfiguration` option specifies which configuration is used by default. When `defaultConfiguration` is not set, `options` are used directly without modification.

----
url: https://angular.dev/tools/cli/deployment
----

When you are ready to deploy your Angular application to a remote server, you have various options.

## [Automatic deployment with the CLI](#automatic-deployment-with-the-cli)

The Angular CLI command `ng deploy` executes the `deploy` [CLI builder](tools/cli/cli-builder) associated with your project. A number of third-party builders implement deployment capabilities to different platforms. You can add any of them to your project with `ng add`.

When you add a package with deployment capability, it will automatically update your workspace configuration (`angular.json` file) with a `deploy` section for the selected project. You can then use the `ng deploy` command to deploy that project.

For example, the following command automatically deploys a project to [Firebase](https://firebase.google.com/).

```
ng add @angular/fire
ng deploy
```

The command is interactive. In this case, you must have or create a Firebase account and authenticate using it. The command prompts you to select a Firebase project for deployment before building your application and uploading the production assets to Firebase.

The table below lists tools which implement deployment functionality to different platforms. The `deploy` command for each package may require different command line options. You can read more by following the links associated with the package names below:

| Deployment to                                                     | Setup Command                                                                               |
| :---------------------------------------------------------------- | :------------------------------------------------------------------------------------------ |
| [Firebase hosting](https://firebase.google.com/docs/hosting)      | [`ng add @angular/fire`](https://www.npmjs.com/package/@angular/fire)                       |
| [Vercel](https://vercel.com/solutions/angular)                    | [`vercel init angular`](https://github.com/vercel/vercel/tree/main/examples/angular)        |
| [Netlify](https://www.netlify.com)                                | [`ng add @netlify-builder/deploy`](https://www.npmjs.com/package/@netlify-builder/deploy)   |
| [GitHub Pages](https://pages.github.com)                          | [`ng add angular-cli-ghpages`](https://www.npmjs.com/package/angular-cli-ghpages)           |
| [Amazon Cloud S3](https://aws.amazon.com/s3/?nc2=h_ql_prod_st_s3) | [`ng add @jefiozie/ngx-aws-deploy`](https://www.npmjs.com/package/@jefiozie/ngx-aws-deploy) |

If you're deploying to a self-managed server or there's no builder for your favorite cloud platform, you can either [create a builder](tools/cli/cli-builder) that allows you to use the `ng deploy` command, or read through this guide to learn how to manually deploy your application.

## [Manual deployment to a remote server](#manual-deployment-to-a-remote-server)

To manually deploy your application, create a production build and copy the output directory to a web server or content delivery network (CDN). By default, `ng build` uses the `production` configuration. If you have customized your build configurations, you may want to confirm [production optimizations](tools/cli/deployment#production-optimizations) are being applied before deploying.

`ng build` outputs the built artifacts to `dist/my-app/` by default, however this path can be configured with the `outputPath` option in the `@angular/build:application` builder. Copy this directory to the server and configure it to serve the directory.

While this is a minimal deployment solution, there are a few requirements for the server to serve your Angular application correctly.

## [Server configuration](#server-configuration)

This section covers changes you may need to configure on the server to run your Angular application.

### [Routed apps must fall back to `index.html`](#routed-apps-must-fall-back-to-indexhtml)

Client-side rendered Angular applications are perfect candidates for serving with a static HTML server because all the content is static and generated at build time.

If the application uses the Angular router, you must configure the server to return the application's host page (`index.html`) when asked for a file that it does not have.

A routed application should support "deep links". A *deep link* is a URL that specifies a path to a component inside the application. For example, `http://my-app.test/users/42` is a *deep link* to the user detail page that displays the user with `id` 42.

There is no issue when the user initially loads the index page and then navigates to that URL from within a running client. The Angular router performs the navigation *client-side* and does not request a new HTML page.

But clicking a deep link in an email, entering it in the browser address bar, or even refreshing the browser while already on the deep linked page will all be handled by the browser itself, *outside* the running application. The browser makes a direct request to the server for `/users/42`, bypassing Angular's router.

A static server routinely returns `index.html` when it receives a request for `http://my-app.test/`. But most servers by default will reject `http://my-app.test/users/42` and returns a `404 - Not Found` error *unless* it is configured to return `index.html` instead. Configure the fallback route or 404 page to `index.html` for your server, so Angular is served for deep links and can display the correct route. Some servers call this fallback behavior "Single-Page Application" (SPA) mode.

Once the browser loads the application, Angular router will read the URL to determine which page it is on and display `/users/42` correctly.

For "real" 404 pages such as `http://my-app.test/does-not-exist`, the server does not require any additional configuration. [404 pages implemented in the Angular router](guide/routing/common-router-tasks#displaying-a-404-page) will be displayed correctly.

### [Requesting data from a different server (CORS)](#requesting-data-from-a-different-server-cors)

Web developers may encounter a [*cross-origin resource sharing*](https://developer.mozilla.org/docs/Web/HTTP/CORS "Cross-origin resource sharing") error when making a network request to a server other than the application's own host server. Browsers forbid such requests unless the server explicitly permits them.

There isn't anything Angular or the client application can do about these errors. The *server* must be configured to accept the application's requests. Read about how to enable CORS for specific servers at [enable-cors.org](https://enable-cors.org/server.html "Enabling CORS server").

## [Production optimizations](#production-optimizations)

`ng build` uses the `production` configuration unless configured otherwise. This configuration enables the following build optimization features.

| Features                                                          | Details                                                                                       |
| :---------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- |
| [Ahead-of-Time (AOT) Compilation](tools/cli/aot-compiler)         | Pre-compiles Angular component templates.                                                     |
| [Production mode](tools/cli/deployment#development-only-features) | Optimizes the application for the best runtime performance                                    |
| Bundling                                                          | Concatenates your many application and library files into a minimum number of deployed files. |
| Minification                                                      | Removes excess whitespace, comments, and optional tokens.                                     |
| Mangling                                                          | Renames functions, classes, and variables to use shorter, arbitrary identifiers.              |
| Dead code elimination                                             | Removes unreferenced modules and unused code.                                                 |

See [`ng build`](cli/build) for more about CLI build options and their effects.

### [Development-only features](#development-only-features)

When you run an application locally using `ng serve`, Angular uses the development configuration at runtime which enables:

* Extra safety checks such as [`expression-changed-after-checked`](errors/NG0100) detection.
* More detailed error messages.
* Additional debugging utilities such as the global `ng` variable with [debugging functions](api#angular_core_globals) and [Angular DevTools](tools/devtools) support.

These features are helpful during development, but they require extra code in the app, which is undesirable in production. To ensure these features do not negatively impact bundle size for end users, Angular CLI removes development-only code from the bundle when building for production.

Building your application with `ng build` by default uses the `production` configuration which removes these features from the output for optimal bundle size.

## [`--deploy-url`](#--deploy-url)

`--deploy-url` is a command line option used to specify the base path for resolving relative URLs for assets such as images, scripts, and style sheets at *compile* time.

```
ng build --deploy-url /my/assets
```

The effect and purpose of `--deploy-url` overlaps with [`<base href>`](guide/routing/router-reference#base-href). Both can be used for initial scripts, stylesheets, lazy scripts, and css resources.

Unlike `<base href>` which can be defined in a single place at runtime, the `--deploy-url` needs to be hard-coded into an application at build time. Prefer `<base href>` where possible.

----
url: https://angular.dev/api/forms/signals/LogicFn
----

```
    
      type LogicFn<TValue, TReturn, TPathKind extends PathKind = PathKind.Root> = (  ctx: FieldContext<TValue, TPathKind>,) => TReturn
    
    
```

Jump to details

----
url: https://angular.dev/guide/components/styling
----

Components can optionally include CSS styles that apply to that component's DOM:

```
@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
  styles: `
    img {
      border-radius: 50%;
    }
  `,
})
export class ProfilePhoto {}
```

You can also choose to write your styles in separate files:

```
@Component({
  selector: 'profile-photo',
  templateUrl: 'profile-photo.html',
  styleUrl: 'profile-photo.css',
})
export class ProfilePhoto {}
```

When Angular compiles your component, these styles are emitted with your component's JavaScript output. This means that component styles participate in the JavaScript module system. When you render an Angular component, the framework automatically includes its associated styles, even when lazy-loading a component.

Angular works with any tool that outputs CSS, including [Sass](https://sass-lang.com), [Less](https://lesscss.org), and [Stylus](https://stylus-lang.com).

## [Style scoping](#style-scoping)

Every component has a **view encapsulation** setting that determines how the framework scopes a component's styles. There are four view encapsulation modes: `Emulated`, `ShadowDom`, `ExperimentalIsolatedShadowDom`, and `None`. You can specify the mode in the [`@Component`](/api/core/Component) decorator:

```
@Component({
  ...,
  encapsulation: ViewEncapsulation.None,
})
export class ProfilePhoto { }
```

### [ViewEncapsulation.Emulated](#viewencapsulationemulated)

By default, Angular uses emulated encapsulation so that a component's styles only apply to elements defined in that component's template. In this mode, the framework generates a unique HTML attribute for each component instance, adds that attribute to elements in the component's template, and inserts that attribute into the CSS selectors defined in your component's styles.

This mode ensures that a component's styles do not leak out and affect other components. However, global styles defined outside of a component may still affect elements inside a component with emulated encapsulation.

In emulated mode, Angular supports the [`:host`](https://developer.mozilla.org/docs/Web/CSS/:host) pseudo-class. While the [`:host-context()`](https://developer.mozilla.org/docs/Web/CSS/:host-context) pseudo-class is deprecated in modern browsers, Angular's compiler provides full support for it. Both pseudo-classes can be used without relying on native [Shadow DOM](https://developer.mozilla.org/docs/Web/Web_Components/Using_shadow_DOM). During compilation, the framework transforms these pseudo classes into attributes so it doesn't comply with these native pseudo classes' rules at runtime (e.g. browser compatibility, specificity). Angular's emulated encapsulation mode does not support any other pseudo classes related to Shadow DOM, such as `::shadow` or `::part`.

#### [`::ng-deep`](#ng-deep)

Angular's emulated encapsulation mode supports a custom pseudo class, `::ng-deep`. **The Angular team strongly discourages new use of `::ng-deep`**. These APIs remain exclusively for backwards compatibility.

When a selector contains `::ng-deep`, Angular stops applying view-encapsulation boundaries after that point in the selector. Any part of the selector that follows `::ng-deep` can match elements outside the component’s template.

For example:

* a CSS rule selector like `p a`, using the emulated encapsulation, will match `<a>` elements that are descendants of a `<p>` element, both being within the component's own template.

* A selector like `::ng-deep p a` will match `<a>` elements anywhere in the application, descendants of a `<p>` element anywhere in the application.

  That effectively makes it behave like a global style.

* In `p ::ng-deep a`, Angular requires the `<p>` element to come from the component's own template, but the `<a>` element may be anywhere in the application.

  So, in effect, the `<a>` element may be in the component's template, or in any of its projected or child content.

* With `:host ::ng-deep p a`, both the `<a>` and `<p>` elements must be descendants of the component's host element.

  They can come from the component's template or the views of its child components, but not elsewhere in the app.

### [ViewEncapsulation.ShadowDom](#viewencapsulationshadowdom)

This mode scopes styles within a component by using [the web standard Shadow DOM API](https://developer.mozilla.org/docs/Web/Web_Components/Using_shadow_DOM). When enabling this mode, Angular attaches a shadow root to the component's host element and renders the component's template and styles into the corresponding shadow tree.

Styles inside the shadow tree cannot affect elements outside of that shadow tree.

Enabling `ShadowDom` encapsulation, however, impacts more than style scoping. Rendering the component in a shadow tree affects event propagation, interaction with [the `<slot>` API](https://developer.mozilla.org/docs/Web/Web_Components/Using_templates_and_slots), and how browser developer tools show elements. Always understand the full implications of using Shadow DOM in your application before enabling this option.

### [ViewEncapsulation.ExperimentalIsolatedShadowDom](#viewencapsulationexperimentalisolatedshadowdom)

Behaves as above, except this mode strictly guarantees that *only* that component's styles apply to elements in the component's template. Global styles cannot affect elements in a shadow tree and styles inside the shadow tree cannot affect elements outside of that shadow tree.

### [ViewEncapsulation.None](#viewencapsulationnone)

This mode disables all style encapsulation for the component. Any styles associated with the component behave as global styles.

**NOTE:** In `Emulated` and `ShadowDom` modes, Angular doesn't 100% guarantee that your component's styles will always override styles coming from outside it. It is assumed that these styles have the same specificity as your component's styles in case of collision.

## [Defining styles in templates](#defining-styles-in-templates)

You can use the `<style>` element in a component's template to define additional styles. The component's view encapsulation mode applies to styles defined this way.

Angular does not support bindings inside of style elements.

## [Referencing external style files](#referencing-external-style-files)

Component templates can use [the `<link>` element](https://developer.mozilla.org/docs/Web/HTML/Element/link) to reference CSS files. Additionally, your CSS may use [the `@import`at-rule](https://developer.mozilla.org/docs/Web/CSS/@import) to reference CSS files. Angular treats these references as *external* styles. External styles are not affected by emulated view encapsulation.

## [Namespacing CSS custom properties](#namespacing-css-custom-properties)

Angular can add a prefix to the CSS custom properties (also called CSS variables) that your component styles declare and read. Custom properties inherit down the DOM tree, so when something outside your application defines a custom property such as `--primary-color` on an ancestor element, your components read that value. This matters when your application shares a page with another application or with markup you do not control. Angular does not namespace custom properties until you ask it to, and an application that owns its page does not need namespacing.

To scope the custom properties in your component styles to your application, add [`provideCssVarNamespacing`](api/platform-browser/provideCssVarNamespacing) to your application's providers. It uses the application's [`APP_ID`](api/core/APP_ID) as the namespace:

### app.config.ts

```
import {APP_ID, ApplicationConfig} from '@angular/core';
import {provideCssVarNamespacing} from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [{provide: APP_ID, useValue: 'my-app'}, provideCssVarNamespacing()],
};
```

Angular prefixes the custom properties in your component styles with that namespace followed by an underscore, so `--primary-color` becomes `--my-app_primary-color`. The prefix applies to declarations, `var()` references, `@property` rules, and style bindings such as `[style.--primary-color]`, including the style bindings a component declares in its `host` object.

[`APP_ID`](/api/core/APP_ID) is `ng` unless you set it, so give each application on the page its own [`APP_ID`](/api/core/APP_ID). Otherwise, the applications share a prefix and collide again. To use a namespace that differs from the application id, pass it to [`provideCssVarNamespacing`](/api/platform-browser/provideCssVarNamespacing). Angular appends the underscore itself: `provideCssVarNamespacing('my-app_')` produces `--my-app__primary-color`.

Angular namespaces the styles it compiles into a component: the `styles` and `styleUrl` of the component, the styles you [write in a `<style>` element](#defining-styles-in-templates) in its template, stylesheets its template references with a relative `<link rel="stylesheet">`, and the styles of a component that uses [`ViewEncapsulation.None`](/api/core/ViewEncapsulation#None). Angular does not namespace a stylesheet the browser loads at runtime, such as a global stylesheet your build configuration lists or an [external style](#referencing-external-style-files) that your build does not inline.

Namespacing applies to every component Angular compiles, including the components of the libraries you install. When a library's styles read a custom property that a global stylesheet defines, such as the properties of a theme, the reference no longer matches, the browser uses the `var()` fallback if there is one or otherwise [the property's inherited or initial value](https://www.w3.org/TR/css-variables-1/#invalid-variables), and nothing reports an error. Before you enable namespacing in an existing application, review the custom properties that cross between your global stylesheets and your components.

**IMPORTANT:** Angular rewrites custom property names only in the styles and bindings it compiles. Everywhere else keeps the name you write, and nothing reports the mismatch.

Angular does not rewrite the name in:

* Static style attributes, such as `style="--primary-color: red"`, including the `style` entry of a component's `host` object.
* Object and string style bindings, such as `[style]="{'--primary-color': color}"`, `[style]="'--primary-color: red'"` and [`ngStyle`](/api/common/NgStyle), including the `[style]` entry of a component's `host` object.
* Custom property names inside a binding's value, such as `[style.border-color]="'var(--primary-color)'"`.
* Calls to [`Renderer2.setStyle`](/api/core/Renderer2#setStyle).

Each of these produces a property that your namespaced styles no longer read. Namespace those names yourself, as described in [Using namespaced properties in TypeScript](#using-namespaced-properties-in-typescript).

### [Opting out of namespacing](#opting-out-of-namespacing)

To declare or read a custom property that Angular does not namespace, such as one defined in a global stylesheet, prefix its name with `--global--`. Angular removes `--global` and leaves the remaining `--` and the rest of the name unchanged:

```
:host {
  /* Declares --accent-color and reads --brand-color, not --my-app_brand-color. */
  --global--accent-color: navy;
  color: var(--global--brand-color);
}
```

Write two hyphens after `global`. A single hyphen, as in `--global-brand-color`, does not opt out, and Angular namespaces that name like any other. Avoid names that start with `--global-` followed by anything but a second hyphen; a future major version is planned to reject them at build time. Angular removes `--global` in every application, including applications that never configure a namespace.

### [Using namespaced properties in TypeScript](#using-namespaced-properties-in-typescript)

Prefer a style binding such as `[style.--primary-color]`, which Angular namespaces for you. When you go through a DOM API instead, pass the name you wrote in your styles to [`CssVarNamespacer`](api/platform-browser/CssVarNamespacer), including the leading `--`:

### profile-photo.ts

```
import {Component, ElementRef, inject} from '@angular/core';
import {CssVarNamespacer} from '@angular/platform-browser';

@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
  styles: `
    img {
      border: 2px solid var(--primary-color);
    }
  `,
})
export class ProfilePhoto {
  private readonly host: HTMLElement = inject(ElementRef).nativeElement;
  private readonly cssVarNamespacer = inject(CssVarNamespacer);

  setPrimaryColor(color: string): void {
    this.host.style.setProperty(this.cssVarNamespacer.namespace('--primary-color'), color);
  }
}
```

Use the plain name for a property you declared with `--global--`, since Angular never namespaces those. For everything else, `namespace` returns the name unchanged when an application configures no namespace, so a library can call it for the custom properties its own styles declare.

----
url: https://angular.dev/api/forms/TouchedChangeEvent
----

```
    
      class TouchedChangeEvent extends ControlEvent {}
    
    
```

@paramtouched`boolean`

@paramsource`AbstractControl<any, any, any>`

@returns`TouchedChangeEvent`

Jump to details

----
url: https://angular.dev/api/router/UrlSegment
----

```
    
      class UrlSegment {}
    
    
```

@parampath`string`

The path part of a URL segment

@paramparameters`{ [name: string]: string; }`

The matrix parameters associated with a segment

@returns`UrlSegment`

The path part of a URL segment

The matrix parameters associated with a segment

## [Description](#description)

Represents a single URL segment.

A UrlSegment is a part of a URL between the two slashes. It contains a path and the matrix parameters associated with the segment.

## [Usage Notes](#usage-notes)

 ### Example

```
@Component({templateUrl:'template.html'})
class MyComponent {
  constructor(router: Router) {
    const tree: UrlTree = router.parseUrl('/team;id=33');
    const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];
    const s: UrlSegment[] = g.segments;
    s[0].path; // returns 'team'
    s[0].parameters; // returns {id: 33}
  }
}
```

Jump to details

----
url: https://angular.dev/cli/version
----

```
ng version [options]
```

```
ng v [options]
```

Outputs Angular CLI version.

## [Options](#options)

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`json`

Outputs version information in JSON format.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/essentials/templates
----

# Templates

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/templates.md "Edit this page")

Use Angular's template syntax to create dynamic user interfaces.

Component templates aren't just static HTML— they can use data from your component class and set up handlers for user interaction.

## [Showing dynamic text](#showing-dynamic-text)

In Angular, a *binding* creates a dynamic connection between a component's template and its data. This connection ensures that changes to the component's data automatically update the rendered template.

You can create a binding to show some dynamic text in a template by using double curly-braces:

```
@Component({
  selector: 'user-profile',
  template: `<h1>Profile for {{ userName() }}</h1>`,
})
export class UserProfile {
  userName = signal('pro_programmer_123');
}
```

When Angular renders the component, you see:

```
<h1>Profile for pro_programmer_123</h1>
```

Angular automatically keeps the binding up-to-date when the value of the signal changes. Building on the example above, if we update the value of the `userName` signal:

```
this.userName.set('cool_coder_789');
```

The rendered page updates to reflect the new value:

```
<h1>Profile for cool_coder_789</h1>
```

## [Setting dynamic properties and attributes](#setting-dynamic-properties-and-attributes)

Angular supports binding dynamic values into DOM properties with square brackets:

```
@Component({
  /*...*/
  // Set the `disabled` property of the button based on the value of `isValidUserId`.
  template: `<button [disabled]="!isValidUserId()">Save changes</button>`,
})
export class UserProfile {
  isValidUserId = signal(false);
}
```

You can also bind to HTML *attributes* by prefixing the attribute name with `attr.`:

```
<!-- Bind the `role` attribute on the `<ul>` element to value of `listRole`. -->
<ul [attr.role]="listRole()"></ul>
```

Angular automatically updates DOM properties and attributes when the bound value changes.

## [Handling user interaction](#handling-user-interaction)

Angular lets you add event listeners to an element in your template with parentheses:

```
@Component({
  /*...*/
  // Add an 'click' event handler that calls the `cancelSubscription` method.
  template: `<button (click)="cancelSubscription()">Cancel subscription</button>`,
})
export class UserProfile {
  /* ... */

  cancelSubscription() {
    /* Your event handling code goes here. */
  }
}
```

If you need to pass the [event](https://developer.mozilla.org/docs/Web/API/Event) object to your listener, you can use Angular's built-in `$event` variable inside the function call:

```
@Component({
  /*...*/
  // Add an 'click' event handler that calls the `cancelSubscription` method.
  template: `<button (click)="cancelSubscription($event)">Cancel subscription</button>`,
})
export class UserProfile {
  /* ... */

  cancelSubscription(event: Event) {
    /* Your event handling code goes here. */
  }
}
```

## [Control flow with `@if` and `@for`](#control-flow-with-if-and-for)

You can conditionally hide and show parts of a template with Angular's `@if` block:

```
<h1>User profile</h1>

@if (isAdmin()) {
  <h2>Admin settings</h2>
  <!-- ... -->
}
```

The `@if` block also supports an optional `@else` block:

```
<h1>User profile</h1>

@if (isAdmin()) {
  <h2>Admin settings</h2>
  <!-- ... -->
} @else {
  <h2>User settings</h2>
  <!-- ... -->
}
```

You can repeat part of a template multiple times with Angular's `@for` block:

```
<h1>User profile</h1>

<ul class="user-badge-list">
  @for (badge of badges(); track badge.id) {
    <li class="user-badge">{{ badge.name }}</li>
  }
</ul>
```

Angular uses the `track` keyword, shown in the example above, to associate data with the DOM elements created by `@for`. See [*Why is track in @for blocks important?*](guide/templates/control-flow#why-is-track-in-for-blocks-important) for more info.

**TIP:** Want to know more about Angular templates? See the [In-depth Templates guide](guide/templates) for the full details.

## [Next Step](#next-step)

Now that you have dynamic data and templates in the application, it's time to learn how to enhance templates by conditionally hiding or showing certain elements, looping over elements, and more.

----
url: https://angular.dev/guide/forms/signals/cross-field-logic
----

**Cross-field logic** is necessary when any rule, validation, or behavior of one field depends on another field's value or state.

Signal forms provide a **field context** to every rule function. The field context provides access to the current field's value and state, and lets you read other fields in the form using `valueOf()`, `stateOf()`, and `fieldTreeOf()`.

This guide covers the field context API in depth and shows common cross-field patterns. For single-field validation, see the [Validation guide](/guide/forms/signals/validation).

## [Understanding the field context](#understanding-the-field-context)

Every rule function in signal forms receives a **field context** parameter, which is an object that describes the current field and provides access to the rest of the form.

There are three properties you can access for the current field:

| Property    | Type                 | Description                                                          |
| ----------- | -------------------- | -------------------------------------------------------------------- |
| `value`     | `Signal<TValue>`     | The current field's value as a signal                                |
| `state`     | `FieldState<TValue>` | The current field's state (such as validity, errors, touched, dirty) |
| `fieldTree` | `FieldTree<TValue>`  | The current field's tree, for programmatic access to child fields    |

For cross-field logic, the following three properties allow you to access other parts of the form:

| Property        | Type                           | Description                                                                                                                        |
| --------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| `valueOf()`     | `(path) => PValue`             | Most common. Use when you need another field's raw value for comparisons or calculations.                                          |
| `stateOf()`     | `(path) => FieldState<PValue>` | Use when your logic depends on another field's state, such as whether it's valid, touched, or dirty.                               |
| `fieldTreeOf()` | `(path) => FieldTree<PModel>`  | Use when you need programmatic access to another field's tree, such as pushing errors to a specific child field with validateTree. |

Here is an example of using `value` and `valueOf()` to validate that the current field (end date) comes after the start date in the form:

```
import {Component, signal} from '@angular/core';
import {form, validate} from '@angular/forms/signals';

@Component({/* ... */})
export class EventForm {
  eventModel = signal({
    startDate: new Date('2026-06-01'),
    endDate: new Date('2026-06-05'),
  });

  eventForm = form(this.eventModel, (schemaPath) => {
    validate(schemaPath.endDate, ({value, valueOf}) => {
      if (value() <= valueOf(schemaPath.startDate)) {
        return {
          kind: 'invalidDateRange',
          message: 'End date must be after start date',
        };
      }

      return null;
    });
  });
}
```

**NOTE:** The `fieldContext` parameter is typically destructured to pull out only what the rule needs. The remaining examples in this guide use this pattern.

## [Cross-field validation patterns](#cross-field-validation-patterns)

The date range example from the previous section validates the end date against the start date. Because the rule reads `valueOf(schemaPath.startDate)`, it re-evaluates automatically whenever either date changes. In other words, a single validator is enough to keep the error state correct.

However, that single validator only places the error on the end date field. If you want both fields to show an error when the range is invalid, add a matching validation rule to each field:

```
import {Component, signal} from '@angular/core';
import {form, validate} from '@angular/forms/signals';

@Component({/* ... */})
export class EventForm {
  eventModel = signal({
    startDate: new Date('2026-06-01'),
    endDate: new Date('2026-06-05'),
  });

  eventForm = form(this.eventModel, (schemaPath) => {
    validate(schemaPath.startDate, ({value, valueOf}) => {
      if (value() >= valueOf(schemaPath.endDate)) {
        return {
          kind: 'invalidDateRange',
          message: 'Start date must be before end date',
        };
      }
      return null;
    });

    validate(schemaPath.endDate, ({value, valueOf}) => {
      if (value() <= valueOf(schemaPath.startDate)) {
        return {
          kind: 'invalidDateRange',
          message: 'End date must be after start date',
        };
      }
      return null;
    });
  });
}
```

Both rules make use of `valueOf()` to read the other field. Because each rule is reactive, changing either date re-evaluates both validations automatically.

**NOTE:** When a rule involves multiple fields, you need to decide where the error belongs: on a specific field, on multiple fields, or on the parent. In general, place the error where the user would most likely go to fix the problem.

### [Conditional requirements](#conditional-requirements)

In some forms, certain fields are only required under certain conditions. For example, a registration form might require a company name only when the user selects a business account type:

```
import {Component, signal} from '@angular/core';
import {form, required} from '@angular/forms/signals';

@Component({/* ... */})
export class RegistrationForm {
  registrationModel = signal({
    accountType: 'personal' as 'personal' | 'business',
    companyName: '',
  });

  registrationForm = form(this.registrationModel, (schemaPath) => {
    required(schemaPath.companyName, {
      when: ({valueOf}) => valueOf(schemaPath.accountType) === 'business',
      message: 'Company name is required for business accounts',
    });
  });
}
```

The `when` option receives the same field context as any other rule function, so `valueOf` works the same way. When the user switches back to `'personal'`, the condition re-evaluates and the requirement — along with its error — clears automatically.

Using `required()` with `when` instead of a manual [`validate()`](/api/forms/signals/validate) check also adds proper required metadata to the field, which enables accessibility features like marking the field as required for screen readers.

### [Validating based on another field's state](#validating-based-on-another-fields-state)

The examples so far use `valueOf()` to read another field's value. Sometimes your logic depends on another field's *state* instead — whether it's valid, touched, or dirty. Use `stateOf()` for this.

For example, a confirm-password field should only check for a match once the user has interacted with the password field. If the user hasn't touched the password yet, flagging a mismatch on the confirmation is premature:

```
import {Component, signal} from '@angular/core';
import {form, validate} from '@angular/forms/signals';

@Component({/* ... */})
export class PasswordForm {
  passwordModel = signal({
    password: '',
    confirmPassword: '',
  });

  passwordForm = form(this.passwordModel, (schemaPath) => {
    validate(schemaPath.confirmPassword, ({value, valueOf, stateOf}) => {
      if (!stateOf(schemaPath.password).touched()) {
        return null;
      }
      if (value() !== valueOf(schemaPath.password)) {
        return {
          kind: 'passwordMismatch',
          message: 'Passwords do not match',
        };
      }
      return null;
    });
  });
}
```

The `stateOf()` call returns the other field's [field state](api/forms/signals/FieldState), giving you access to signals like `invalid()`, `touched()`, and `dirty()`. Because these are signals, the rule re-evaluates whenever the password field's validity changes.

**CRITICAL:** Be careful not to read state which depends on your field's validation, as that creates a circular loop. For example, a validator which checks whether the parent field is valid will create an infinite loop because the parent's validity depends on its children's validity (which includes your validator).

## [Using validateTree](#using-validatetree)

The examples so far use [`validate()`](/api/forms/signals/validate) to check individual fields. Sometimes you need to validate a group of fields where the logic is inherently about multiple fields in a group, and direct errors to specific children within it. [`validateTree`](/api/forms/signals/validateTree) handles is ideal for these kinds of scenarios.

For example, in a Sudoku puzzle, each row must contain unique numbers. This is a group-level rule: you check the entire row, then flag the specific cells that violate it. This kind of validation can't be expressed cleanly with [`validate`](/api/forms/signals/validate) on individual fields, because each cell would need to know about every other cell.

```
import {Component, signal} from '@angular/core';
import {form, validateTree} from '@angular/forms/signals';

@Component({/* ... */})
export class SudokuRow {
  rowModel = signal({
    cell1: 1,
    cell2: 3,
    cell3: 1,
    cell4: 4,
  });

  rowForm = form(this.rowModel, (schemaPath) => {
    validateTree(schemaPath, ({value, fieldTreeOf}) => {
      const row = value();
      const entries = [
        {val: row.cell1, fieldTree: fieldTreeOf(schemaPath.cell1)},
        {val: row.cell2, fieldTree: fieldTreeOf(schemaPath.cell2)},
        {val: row.cell3, fieldTree: fieldTreeOf(schemaPath.cell3)},
        {val: row.cell4, fieldTree: fieldTreeOf(schemaPath.cell4)},
      ];

      const counts = new Map<number, number>();
      for (const {val} of entries) {
        if (val !== 0) {
          counts.set(val, (counts.get(val) ?? 0) + 1);
        }
      }

      const errors = entries
        .filter(({val}) => val !== 0 && (counts.get(val) ?? 0) > 1)
        .map(({val, fieldTree}) => ({
          kind: 'duplicateInRow',
          message: `${val} already appears in this row`,
          fieldTree,
        }));

      return errors.length > 0 ? errors : null;
    });
  });
}
```

The validator runs on the parent field (the row), reads all cell values, counts duplicates, and returns an error for each cell that contains a repeated number. The `fieldTree` property on each error tells Angular exactly which cell should show the error. Without `fieldTree`, the errors would apply to the row itself — not where the user needs to see them.

Because [`validateTree`](/api/forms/signals/validateTree) can return an array of errors, a single validator can flag multiple cells at once. Each error includes a `fieldTree` pointing to its target, so Angular routes the errors to the correct fields.

### [When to use validateTree vs validate](#when-to-use-validatetree-vs-validate)

Prefer [`validate()`](/api/forms/signals/validate) with `valueOf()` when the error belongs on the field being validated — even if the rule reads from other fields. Reach for [`validateTree`](/api/forms/signals/validateTree) when:

* The validation logic is inherently about a group of fields, not any single field
* The validator needs to return errors targeting different child fields

**TIP:** For an introduction to [`validateTree`](/api/forms/signals/validateTree) and its return type, see the [Validation guide](/guide/forms/signals/validation).

## [Next steps](#next-steps)

This guide covered the field context API and common cross-field patterns. To learn more about related Signal Forms guide, check out:

----
url: https://angular.dev/api/platform-browser/HydrationFeatureKind
----

```
    
      enum HydrationFeatureKind {}
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/selenium-webdriver/WebDriverHarnessEnvironmentOptions
----

```
    
      interface WebDriverHarnessEnvironmentOptions {}
    
    
```

The query function used to find DOM elements.

Jump to details

----
url: https://angular.dev/api/platform-server/RuntimeErrorCode
----

```
    
      enum RuntimeErrorCode {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ForwardRefFn
----

```
    
      interface ForwardRefFn {  (): any;}
    
    
```

@returns`any`

## [Usage Notes](#usage-notes)

### [Example](#example)

```
const ref = forwardRef(() => Lock);
```

Jump to details

----
url: https://angular.dev/api/upgrade/static/testing/createAngularTestingModule
----

```
function createAngularTestingModule(
  angularJSModules: string[],
  strictDi?: boolean | undefined,
): Type<any>;
```

@paramangularJSModules`string[]`

a collection of the names of AngularJS modules to include in the configuration.

@paramstrictDi`boolean | undefined`

whether the AngularJS injector should have `strictDI` enabled.

@returns`Type<any>`

## [Description](#description)

A helper function to use when unit testing Angular services that depend upon upgraded AngularJS services.

This function returns an [`NgModule`](/api/core/NgModule) decorated class that is configured to wire up the Angular and AngularJS injectors without the need to actually bootstrap a hybrid application. This makes it simpler and faster to unit test services.

Use the returned class as an "import" when configuring the [`TestBed`](/api/core/testing/TestBed).

In the following code snippet, we are configuring the TestBed with two imports. The `Ng2AppModule` is the Angular part of our hybrid application and the `ng1AppModule` is the AngularJS part.

```
import {TestBed} from '@angular/core/testing';
import {
  createAngularJSTestingModule,
  createAngularTestingModule,
} from '@angular/upgrade/static/testing';

import {HeroesService, ng1AppModule, Ng2AppModule} from './module';

const {module, inject} = (window as any).angular.mock;

beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [createAngularTestingModule([ng1AppModule.name]), Ng2AppModule],
    });
  });
```

Once this is done we can get hold of services via the Angular [`Injector`](/api/core/Injector) as normal. Services that are (or have dependencies on) an upgraded AngularJS service, will be instantiated as needed by the AngularJS `$injector`.

In the following code snippet, `HeroesService` is an Angular service that depends upon an AngularJS service, `titleCase`.

```
it('should have access to the HeroesService', () => {
    const heroesService = TestBed.inject(HeroesService);
    expect(heroesService).toBeDefined();
  });
```

This helper is for testing services not Components. For Component testing you must still bootstrap a hybrid app. See `UpgradeModule` or `downgradeModule` for more information.

The resulting configuration does not wire up AngularJS digests to Zone hooks. It is the responsibility of the test writer to call `$rootScope.$apply`, as necessary, to trigger AngularJS handlers of async events from Angular.

The helper sets up global variables to hold the shared Angular and AngularJS injectors.

* Only call this helper once per spec.
* Do not use [`createAngularTestingModule`](/api/upgrade/static/testing/createAngularTestingModule) in the same spec as [`createAngularJSTestingModule`](/api/upgrade/static/testing/createAngularJSTestingModule).

Here is the example application and its unit tests that use [`createAngularTestingModule`](/api/upgrade/static/testing/createAngularTestingModule) and [`createAngularJSTestingModule`](/api/upgrade/static/testing/createAngularJSTestingModule).

Jump to details

----
url: https://angular.dev/guide/animations
----

Well-designed animations can make your application more intuitive and engaging, but they aren't just cosmetic. Animations can improve your application and the user experience in a number of ways:

* Without animations, web page transitions can seem abrupt and jarring
* Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions
* Good animations can smoothly direct the user's attention throughout a workflow

Angular provides `animate.enter` and `animate.leave` to animate your application's elements. These two features apply enter and leave CSS classes at the appropriate times or call functions to apply animations from third party libraries. `animate.enter` and `animate.leave` are not directives. They are special API supported directly by the Angular compiler. They can be used directly on elements and also as a host binding.

## [`animate.enter`](#animateenter)

You can use `animate.enter` to animate elements as they *enter* the DOM. You can define enter animations using CSS classes with either transitions or keyframe animations.

### enter.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-enter',
  templateUrl: 'enter.html',
  styleUrls: ['enter.css'],
})
export class Enter {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### enter.html

```
<h2><code>animate.enter</code> Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="enter-container" animate.enter="enter-animation">
    <p>The box is entering.</p>
  </div>
}
```

### enter.css

```
:host {
  display: block;
  height: 200px;
}

.enter-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
}

.enter-container p {
  margin: 0;
}

.enter-animation {
  animation: slide-fade 1s;
}

@keyframes slide-fade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

When the animation completes, Angular removes the class or classes that you specified in `animate.enter` from the DOM. Animation classes are only present while the animation is active.

**NOTE:** When using multiple keyframe animations or transition properties on an element, Angular removes all classes only *after* the longest animation has completed.

You can use `animate.enter` with any other Angular features, such as control flow or dynamic expressions. `animate.enter` accepts both a single class string (with multiple classes separated by spaces), or an array of class strings.

A quick note about using CSS transitions: If you choose to use transitions instead of keyframe animations, the classes added to the element with `animate.enter` represent the state that the transition will animate *to*. Your base element CSS is what the element will look like when no animations run, which is likely similar to the end state of the CSS transition. So you would still need to pair it with `@starting-style` to have an appropriate *from* state for your transition to work.

### enter-binding.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-enter-binding',
  templateUrl: 'enter-binding.html',
  styleUrls: ['enter-binding.css'],
})
export class EnterBinding {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }

  enterClass = signal('enter-animation');
}
```

### enter-binding.html

```
<h2><code>animate.enter</code> Binding Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="enter-container" [animate.enter]="enterClass()">
    <p>The box is entering.</p>
  </div>
}
```

### enter-binding.css

```
:host {
  display: block;
  height: 200px;
}

.enter-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
}

.enter-container p {
  margin: 0;
}

.enter-animation {
  animation: slide-fade 1s;
}

@keyframes slide-fade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

## [`animate.leave`](#animateleave)

You can use `animate.leave` to animate elements as they *leave* the DOM. You can define leave animations using CSS classes with either transforms or keyframe animations.

### leave.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave',
  templateUrl: 'leave.html',
  styleUrls: ['leave.css'],
})
export class Leave {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### leave.html

```
<h2><code>animate.leave</code> Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-container" animate.leave="leaving">
    <p>Goodbye</p>
  </div>
}
```

### leave.css

```
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

When the animation completes, Angular automatically removes the animated element from the DOM.

**NOTE:** When using multiple keyframe animations or transition properties on an element, Angular waits to remove the element only *after* the longest of those animations has completed.

`animate.leave` can also be used with signals, and other bindings. You can use `animate.leave` with a single class or multiple classes. Either specify it as a simple string with spaces or a string array.

### leave-binding.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave-binding',
  templateUrl: 'leave-binding.html',
  styleUrls: ['leave-binding.css'],
})
export class LeaveBinding {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }

  farewell = signal('leaving');
}
```

### leave-binding.html

```
<h2><code>animate.leave</code> Binding Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-container" [animate.leave]="farewell()">
    <p>Goodbye</p>
  </div>
}
```

### leave-binding.css

```
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

### [Element removal order](#element-removal-order)

There is some nuance to how `animate.leave` animations are run and when an animation will occur. `animate.leave` works if it is placed on the element that is being removed, and if `animate.leave` is placed on an element that is a *descendant* of the element being removed *within the same component template*, those child `animate.leave` animations will happen *before* the parent node is removed from the DOM. This ensures that you can confidently animate away child elements without the parent node disappearing prematurely.

### leave-parent.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave-parent',
  templateUrl: 'leave-parent.html',
  styleUrls: ['leave-parent.css'],
})
export class LeaveParent {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### leave-parent.html

```
<h2><code>animate.leave</code> Parent Sub-tree Animation Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-parent">
    <!-- the child node with `animate.leave` will animate first
         before the parent node is removed from the DOM. -->
    <div class="leave-container" animate.leave="leaving">
      <p>Goodbye</p>
    </div>
  </div>
}
```

### leave-parent.css

```
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

**IMPORTANT:** Child animations fire only for elements within the same component template. If an element being removed contains child components, any `animate.leave` animations defined inside those child component templates will **not** run before the parent is removed. To animate a child component on removal, apply `animate.leave` to the child component's host element directly within the parent template instead, or programmatically handle triggering the animation in the child component and delaying the removal of the parent until that animation completes.

## [Event Bindings, Functions, and Third-party Libraries](#event-bindings-functions-and-third-party-libraries)

Both `animate.enter` and `animate.leave` support event binding syntax that allows for function calls. You can use this syntax to call a function in your component code or utilize third-party animation libraries, like [GSAP](https://gsap.com/), [anime.js](https://animejs.com/), or any other JavaScript animation library.

### leave-event.ts

```
import {AnimationCallbackEvent, Component, signal} from '@angular/core';

@Component({
  selector: 'app-leave-binding',
  templateUrl: 'leave-event.html',
  styleUrls: ['leave-event.css'],
})
export class LeaveEvent {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }

  leavingFn(event: AnimationCallbackEvent) {
    // Example of calling GSAP
    // gsap.to(event.target, {
    //   duration: 1,
    //   x: 100,
    //   // arrow functions are handy for concise callbacks
    //   onComplete: () => event.animationComplete()
    // });
    event.animationComplete();
  }
}
```

### leave-event.html

```
<h2><code>animate.leave</code> Function Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>

@if (isShown()) {
  <div class="leave-container" (animate.leave)="leavingFn($event)">
    <p>Goodbye</p>
  </div>
}
```

### leave-event.css

```
:host {
  display: block;
  height: 200px;
}

.leave-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.leave-container p {
  margin: 0;
}

.leaving {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

The `$event` object has the type [`AnimationCallbackEvent`](/api/core/AnimationCallbackEvent). It includes the element as the `target` and provides an `animationComplete()` function to notify the framework when the animation finishes.

**IMPORTANT:** You **must** call the `animationComplete()` function when using `animate.leave` for Angular to remove the element.

If you don't call `animationComplete()` when using `animate.leave`, Angular calls the function automatically after a four-second delay. You can configure the duration of the delay by providing the token [`MAX_ANIMATION_TIMEOUT`](/api/core/MAX_ANIMATION_TIMEOUT) in milliseconds.

```
{ provide: MAX_ANIMATION_TIMEOUT, useValue: 6000 }
```

## [Compatibility with Legacy Angular Animations](#compatibility-with-legacy-angular-animations)

You cannot use legacy animations alongside `animate.enter` and `animate.leave` within the same component. Doing so would result in enter classes remaining on the element or leaving nodes not being removed. It is otherwise fine to use both legacy animations and the new `animate.enter` and `animate.leave` animations within the same *application*. The only caveat is content projection. If you are projecting content from one component with legacy animations into another component with `animate.enter` or `animate.leave`, or vice versa, this will result in the same behavior as if they are used together in the same component. This is not supported.

## [Testing](#testing)

TestBed provides built-in support for enabling or disabling animations in your test environment. CSS animations require a browser to run, and many of the APIs are not available in a test environment. By default, TestBed disables animations for you in your test environments.

If you want to test that the animations are animating in a browser test, for example an end-to-end test, you can configure TestBed to enable animations by specifying `animationsEnabled: true` in your test configuration.

```
TestBed.configureTestingModule({animationsEnabled: true});
```

This will configure animations in your test environment to behave normally.

**NOTE:** Some test environments do not emit animation events like `animationstart`, `animationend` and their transition event equivalents.

## [More on Angular animations](#more-on-angular-animations)

You might also be interested in the following:

----
url: https://angular.dev/extended-diagnostics/NG8116
----

This diagnostic ensures that a standalone component using custom structural directives (e.g., `*select` or `*featureFlag`) in a template has the necessary imports for those directives.

```
import {Component} from '@angular/core';

@Component({
  // Template uses `*select`, but no corresponding directive imported.
  imports: [],
  template: `<p *select="let data; from: source">{{ data }}</p>`,
})
class MyComponent {}
```

## [What's wrong with that?](#whats-wrong-with-that)

Using a structural directive without importing it will fail at runtime, as Angular attempts to bind to a `select` property of the HTML element, which does not exist.

## [What should I do instead?](#what-should-i-do-instead)

Make sure that the corresponding structural directive is imported into the component:

```
import {Component} from '@angular/core';
import {SelectDirective} from 'my-directives';

@Component({
  // Add `SelectDirective` to the `imports` array to make it available in the template.
  imports: [SelectDirective],
  template: `<p *select="let data; from: source">{{ data }}</p>`,
})
class MyComponent {}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `missingStructuralDirective` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "missingStructuralDirective": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/router/UrlSegmentGroup
----

## [API](/api/router/UrlSegmentGroup#api)

```
    
      class UrlSegmentGroup {}
    
    
```

@paramsegments`UrlSegment[]`

The URL segments of this group. See [`UrlSegment`](/api/router/UrlSegment) for more information

@paramchildren`{ [key: string]: UrlSegmentGroup; }`

The list of children of this group

@returns`UrlSegmentGroup`

The parent node in the url tree

The URL segments of this group. See [`UrlSegment`](/api/router/UrlSegment) for more information

The list of children of this group

Whether the segment has child segments

@returns`boolean`

Number of child segments

## [Description](/api/router/UrlSegmentGroup#description)

Represents the parsed URL segment group.

See [`UrlTree`](/api/router/UrlTree) for more information.

Jump to details

----
url: https://angular.dev/cli/generate/resolver
----

```
ng generate resolver [name] [options]
```

```
ng generate r [name] [options]
```

Creates a new resolver in your project. Resolvers are used to pre-fetch data before a route is activated, ensuring that the necessary data is available before the component is displayed. This can improve the user experience by preventing delays and loading states. This schematic generates a new resolver with the specified name and options.

## [Arguments](#arguments)

`name`

The name for the new resolver. This will be used to create the resolver's class and spec files (e.g., `my-resolver.resolver.ts` and `my-resolver.resolver.spec.ts`).

Value Type`string`

## [Options](#options)

`flat`

Creates the new resolver files at the top level of the current project. If set to false, a new folder with the resolver's name will be created to contain the files.

Value Type`boolean`Default`true`

`functional`

Creates the resolver as a function `ResolveFn` instead of a class. Functional resolvers can be simpler for basic scenarios.

Value Type`boolean`Default`true`

`project`

The name of the project where the resolver should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`skip-tests`

Skip the generation of a unit test file `spec.ts` for the new resolver.

Value Type`boolean`Default`false`

`type-separator`

The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.resolver.ts`.

Value Type`string`Allowed Values`-`, `.`Default`-`

Jump to details

----
url: https://angular.dev/guide/aria/grid
----

A grid enables users to navigate two-dimensional data or interactive elements using directional arrow keys, Home, End, and Page Up/Down. Grids work for data tables, calendars, spreadsheets, and layout patterns that group related interactive elements.

### TS

```
import {Component} from '@angular/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

interface Cell {
  rowSpan: number;
  colSpan: number;
  emoji: string;
  explode: boolean;
}

const bomb = '💣';
const emojis = ['🥳', '🤩', '🎉', '🚀', '🔥', '💯', '🦄', '🤯', '💖', '✨', bomb];

function randomSpan(): number {
  const spanChanceTable = [...Array(10).fill(1), ...Array(4).fill(2), ...Array(1).fill(3)];
  const randomIndex = Math.floor(Math.random() * spanChanceTable.length);
  return spanChanceTable[randomIndex];
}

function generateValidGrid(rowCount: number, colCount: number): Cell[][] {
  const grid: Cell[][] = [];
  const visitedCoords = new Set<string>();
  for (let r = 0; r < rowCount; r++) {
    const row = [];
    for (let c = 0; c < colCount; c++) {
      if (visitedCoords.has(`${r},${c}`)) {
        continue;
      }

      const rowSpan = Math.min(randomSpan(), rowCount - r);
      const maxColSpan = Math.min(randomSpan(), colCount - c);
      let colSpan = 1;
      while (colSpan < maxColSpan) {
        if (visitedCoords.has(`${r},${c + colSpan}`)) break;
        colSpan += 1;
      }

      const emoji = emojis[Math.floor(Math.random() * emojis.length)];

      row.push({
        rowSpan,
        colSpan,
        emoji,
        explode: emoji === bomb,
      });

      for (let rs = 0; rs < rowSpan; rs++) {
        for (let cs = 0; cs < colSpan; cs++) {
          visitedCoords.add(`${r + rs},${c + cs}`);
        }
      }
    }
    grid.push(row);
  }
  return grid;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App {
  readonly gridData: Cell[][] = generateValidGrid(6, 6);
}
```

### HTML

```
<table ngGrid #grid="ngGrid">
  @for (row of gridData; track row) {
    <tr ngGridRow>
      @for (cell of row; track cell) {
        @let flipped = {value: false};
        <td ngGridCell [rowSpan]="cell.rowSpan" [colSpan]="cell.colSpan">
          <button
            ngGridCellWidget
            class="card"
            [class.flipped]="flipped.value"
            (click)="flipped.value = true"
          >
            <div class="card-face card-front">
              <svg viewBox="0 0 222 245" xmlns="http://www.w3.org/2000/svg" class="angular-logo">
                <path class="shield-shape" />
              </svg>
            </div>

            <div class="card-face card-back">
              <div [class.explode]="flipped.value && cell.explode">{{ cell.emoji }}</div>
            </div>
          </button>
        </td>
      }
    </tr>
  }
</table>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);

  --card-shadow: 2px 4px 6px rgba(0, 0, 0, 0.5);
}

button {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  outline: none;
}

[ngGrid] {
  display: table;
  border-spacing: 0.75rem;
}

[ngGridCell] {
  height: 4rem;
  width: 4rem;
  perspective: 1000px;
}

.card {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.3s ease-in-out;
  cursor: pointer;
  border-radius: 0.5rem;
  border: 0.25rem solid #f0f0f0;
  box-shadow: var(--card-shadow);
}

.card.flipped {
  transform: rotateY(180deg);
  cursor: default;
}

.card:not(.flipped):hover,
.card:not(.flipped):focus {
  transform: scale(1.05) translate(-2px, -2px);
}

.card:hover,
.card:focus {
  outline-offset: 2px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 90%, transparent);
}

.card-face {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  border-radius: 0.25rem;
}

.card-front {
  background-image: var(--hot-pink-to-electric-violet-radial-gradient);
  color: rgba(0, 0, 0, 0.6);
  overflow: hidden;
}

.card-back {
  background: #f0f0f0;
  transform: rotateY(180deg);
}

.explode {
  animation: shake 0.25s 20 linear;
}

@keyframes shake {
  0%,
  100% {
    transform: translate(0, 0) rotate(0deg);
  }
  20% {
    transform: translate(-3px, -1px) rotate(-1deg);
  }
  40% {
    transform: translate(3px, 1px) rotate(1deg);
  }
  60% {
    transform: translate(-3px, 1px) rotate(-1deg);
  }
  80% {
    transform: translate(3px, -1px) rotate(1deg);
  }
}

.angular-logo {
  transform: rotate(-25deg) scale(1.1) translateY(5%);
}

.shield-shape {
  d: path(
    'm 222.077 39.192 l -8.019 125.923 L 137.387 0 l 84.69 39.192 Z m -53.105 162.825 l -57.933 33.056 l -57.934 -33.056 l 11.783 -28.556 h 92.301 l 11.783 28.556 Z M 111.039 62.675 l 30.357 73.803 H 80.681 l 30.358 -73.803 Z M 7.937 165.115 L 0 39.192 L 84.69 0 L 7.937 165.115 Z'
  );
  fill: currentColor;
}
```

## [Usage](#usage)

Grids work well for data or interactive elements organized in rows and columns where users need keyboard navigation in multiple directions.

**Use grids when:**

* Building interactive data tables with editable or selectable cells
* Creating calendars or date pickers
* Implementing spreadsheet-like interfaces
* Grouping interactive elements (buttons, checkboxes) to reduce tab stops on a page
* Building interfaces requiring two-dimensional keyboard navigation

**Avoid grids when:**

* Displaying simple read-only tables (use semantic HTML `<table>` instead)
* Showing single-column lists (use [Listbox](guide/aria/listbox) instead)
* Displaying hierarchical data (use [Tree](guide/aria/tree) instead)
* Building forms without tabular layout (use standard form controls)

## [Features](#features)

* **Two-dimensional navigation** - Arrow keys move between cells in all directions
* **Focus modes** - Choose between roving tabindex or activedescendant focus strategies
* **Selection support** - Optional cell selection with single or multi-select modes
* **Wrapping behavior** - Configure how navigation wraps at grid edges (continuous, loop, or nowrap)
* **Range selection** - Select multiple cells with modifier keys or dragging
* **Disabled states** - Disable the entire grid or individual cells
* **RTL support** - Automatic right-to-left language navigation

## [Examples](#examples)

### [Data table grid](#data-table-grid)

Use a grid for interactive tables where users need to navigate between cells using arrow keys. This example shows a basic data table with keyboard navigation.

### TS

```
import {
  afterRenderEffect,
  Component,
  computed,
  ElementRef,
  signal,
  viewChild,
  WritableSignal,
} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

type Priority = 'High' | 'Medium' | 'Low';

interface Task {
  taskId: number;
  summary: string;
  priority: Priority;
  assignee: string;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Grid, GridRow, GridCell, GridCellWidget, FormsModule],
})
export class App {
  private readonly _headerCheckbox = viewChild<ElementRef<HTMLInputElement>>('headerCheckbox');

  readonly allSelected = computed(() => this.data().every((t) => t.selected()));
  readonly partiallySelected = computed(
    () => !this.allSelected() && this.data().some((t) => t.selected()),
  );
  readonly data = signal<(Task & {selected: WritableSignal<boolean>})[]>([
    {
      selected: signal(false),
      taskId: 101,
      summary: 'Create Grid Aria Pattern',
      priority: 'High',
      assignee: 'Cyber Cat',
    },
    {
      selected: signal(false),
      taskId: 102,
      summary: 'Build a Pill List example',
      priority: 'Medium',
      assignee: 'Caffeinated Owl',
    },
    {
      selected: signal(false),
      taskId: 103,
      summary: 'Build a Calendar example',
      priority: 'Medium',
      assignee: 'Copybara',
    },
    {
      selected: signal(false),
      taskId: 104,
      summary: 'Build a Data Table example',
      priority: 'Low',
      assignee: 'Rubber Duck',
    },
    {
      selected: signal(false),
      taskId: 105,
      summary: 'Explore Grid possibilities',
      priority: 'High',
      assignee: '[Your Name Here]',
    },
  ]);

  sortAscending: boolean = true;
  tempInput: string = '';

  constructor() {
    afterRenderEffect(() => {
      this._headerCheckbox()!.nativeElement.indeterminate = this.partiallySelected();
    });
  }

  startEdit(
    event: KeyboardEvent | FocusEvent | undefined,
    task: Task,
    inputEl: HTMLInputElement,
  ): void {
    this.tempInput = task.assignee;
    inputEl.focus();

    if (!(event instanceof KeyboardEvent)) return;

    // Start editing with an alphanumeric character.
    if (event.key.length === 1) {
      this.tempInput = event.key;
    }
  }

  onClickEdit(widget: GridCellWidget, task: Task, inputEl: HTMLInputElement) {
    if (widget.isActivated()) return;

    widget.activate();
    setTimeout(() => this.startEdit(undefined, task, inputEl));
  }

  completeEdit(event: KeyboardEvent | FocusEvent | undefined, task: Task): void {
    if (!(event instanceof KeyboardEvent)) {
      return;
    }
    if (event.key === 'Enter') {
      task.assignee = this.tempInput;
    }
  }

  updateSelection(event: Event): void {
    const checked = (event.target as HTMLInputElement).checked;
    this.data().forEach((t) => t.selected.set(checked));
  }

  sortTaskById(): void {
    this.sortAscending = !this.sortAscending;
    if (this.sortAscending) {
      this.data.update((tasks) => tasks.sort((a, b) => a.taskId - b.taskId));
    } else {
      this.data.update((tasks) => tasks.sort((a, b) => b.taskId - a.taskId));
    }
  }
}
```

### HTML

```
<table ngGrid class="basic-data-table">
  <thead>
    <tr ngGridRow>
      <th ngGridCell>
        <input
          ngGridCellWidget
          aria-label="Select all rows"
          type="checkbox"
          [checked]="allSelected()"
          (change)="updateSelection($event)"
          #headerCheckbox
        />
      </th>
      <th ngGridCell>
        <button
          ngGridCellWidget
          class="sort-button"
          aria-label="Sort by ID"
          (click)="sortTaskById()"
        >
          ID
          <span aria-hidden="true" class="material-symbols-outlined" translate="no">
            {{ sortAscending ? 'arrow_upward' : 'arrow_downward' }}
          </span>
        </button>
      </th>
      <th ngGridCell>Task</th>
      <th ngGridCell>Priority</th>
      <th ngGridCell>Assignee</th>
    </tr>
  </thead>
  <tbody>
    @for (task of data(); track task.taskId) {
      <tr ngGridRow>
        <td ngGridCell>
          <input
            ngGridCellWidget
            aria-label="Select row {{ $index + 1 }}"
            type="checkbox"
            [(ngModel)]="task.selected"
          />
        </td>
        <td ngGridCell>{{ task.taskId }}</td>
        <td ngGridCell>{{ task.summary }}</td>
        <td ngGridCell>{{ task.priority }}</td>
        <td ngGridCell class="assignee-cell">
          <div
            type="button"
            ngGridCellWidget
            aria-label="edit assignee"
            widgetType="editable"
            (activated)="startEdit($event, task, assigneeInput)"
            (deactivated)="completeEdit($event, task)"
            #widget="ngGridCellWidget"
          >
            <span [class.hidden]="widget.isActivated()">{{ task.assignee }}</span>
            <input
              [class.hidden]="!widget.isActivated()"
              class="assignee-edit-input"
              [(ngModel)]="tempInput"
              #assigneeInput
            />
            <button
              tabindex="-1"
              aria-label="edit assignee"
              class="material-symbols-outlined assignee-edit-button"
              (click)="onClickEdit(widget, task, assigneeInput)"
              [class.hidden]="widget.isActivated()"
              translate="no"
            >
              edit
            </button>
          </div>
        </td>
      </tr>
    }
  </tbody>
</table>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.hidden {
  display: none;
}

button {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  outline: none;
}

input[type='checkbox'] {
  accent-color: var(--electric-violet);
  transform: scale(1.3);
  outline: none;
  cursor: pointer;
}

[ngGrid] {
  display: table;
  background-color: var(--septenary-contrast);
  border-spacing: 0;
}

[ngGrid] th,
[ngGrid] td {
  padding: 0.75rem 1rem;
}

thead {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

tbody {
  background-color: var(--octonary-contrast);
}

tbody [ngGridRow]:focus-within,
tbody [ngGridRow]:hover {
  background-color: var(--septenary-contrast);
}

[ngGridCell]:focus-within,
[ngGridCell]:hover {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

.sort-button {
  display: flex;
  align-items: center;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 700;
}

.assignee-cell [ngGridCellWidget] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
}

.assignee-edit-button {
  visibility: hidden;
  cursor: pointer;
}

.assignee-cell:focus-within .assignee-edit-button,
.assignee-cell:hover .assignee-edit-button {
  visibility: initial;
}

.assignee-edit-input {
  outline: none;
  border: none;
  color: var(--full-contrast);
  background-color: var(--page-background);
  font-size: 1rem;
  padding: 0.5rem;
}
```

### TS

```
import {
  afterRenderEffect,
  Component,
  computed,
  ElementRef,
  signal,
  viewChild,
  WritableSignal,
} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

type Rank = 'S' | 'A' | 'B' | 'C';

interface Task {
  reward: number;
  target: string;
  rank: Rank;
  hunter: string;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Grid, GridRow, GridCell, GridCellWidget, FormsModule],
})
export class App {
  private readonly _headerCheckbox = viewChild<ElementRef<HTMLInputElement>>('headerCheckbox');

  readonly allSelected = computed(() => this.data().every((t) => t.selected()));
  readonly partiallySelected = computed(
    () => !this.allSelected() && this.data().some((t) => t.selected()),
  );
  readonly data = signal<(Task & {selected: WritableSignal<boolean>})[]>([
    {
      selected: signal(false),
      reward: 50,
      target: '10 Goblins',
      rank: 'C',
      hunter: 'KB Smasher',
    },
    {
      selected: signal(false),
      reward: 999,
      target: '1 Dragon',
      rank: 'S',
      hunter: 'Donkey',
    },
    {
      selected: signal(false),
      reward: 150,
      target: '2 Trolls',
      rank: 'B',
      hunter: 'Meme Spammer',
    },
    {
      selected: signal(false),
      reward: 500,
      target: '1 Demon',
      rank: 'A',
      hunter: 'Dante',
    },
    {
      selected: signal(false),
      reward: 10,
      target: '5 Slimes',
      rank: 'C',
      hunter: '[Help Wanted]',
    },
  ]);

  sortAscending: boolean = true;
  tempInput: string = '';

  constructor() {
    afterRenderEffect(() => {
      this._headerCheckbox()!.nativeElement.indeterminate = this.partiallySelected();
    });
  }

  startEdit(
    event: KeyboardEvent | FocusEvent | undefined,
    task: Task,
    inputEl: HTMLInputElement,
  ): void {
    this.tempInput = task.hunter;
    inputEl.focus();

    if (!(event instanceof KeyboardEvent)) return;

    // Start editing with an alphanumeric character.
    if (event.key.length === 1) {
      this.tempInput = event.key;
    }
  }

  onClickEdit(widget: GridCellWidget, task: Task, inputEl: HTMLInputElement) {
    if (widget.isActivated()) return;

    widget.activate();
    setTimeout(() => this.startEdit(undefined, task, inputEl));
  }

  completeEdit(event: KeyboardEvent | FocusEvent | undefined, task: Task): void {
    if (!(event instanceof KeyboardEvent)) {
      return;
    }
    if (event.key === 'Enter') {
      task.hunter = this.tempInput;
    }
  }

  updateSelection(event: Event): void {
    const checked = (event.target as HTMLInputElement).checked;
    this.data().forEach((t) => t.selected.set(checked));
  }

  sortTaskById(): void {
    this.sortAscending = !this.sortAscending;
    if (this.sortAscending) {
      this.data.update((tasks) => tasks.sort((a, b) => a.reward - b.reward));
    } else {
      this.data.update((tasks) => tasks.sort((a, b) => b.reward - a.reward));
    }
  }
}
```

### HTML

```
<table ngGrid class="retro-data-table">
  <thead>
    <tr ngGridRow>
      <th ngGridCell>
        <input
          ngGridCellWidget
          aria-label="Select all rows"
          type="checkbox"
          [checked]="allSelected()"
          (change)="updateSelection($event)"
          #headerCheckbox
        />
      </th>
      <th ngGridCell>
        <button
          ngGridCellWidget
          class="sort-button"
          aria-label="Sort by ID"
          (click)="sortTaskById()"
        >
          Reward
          <span aria-hidden="true" class="material-symbols-outlined" translate="no">
            {{ sortAscending ? 'arrow_upward' : 'arrow_downward' }}
          </span>
        </button>
      </th>
      <th ngGridCell>Target</th>
      <th ngGridCell>Rank</th>
      <th ngGridCell>Hunter</th>
    </tr>
  </thead>
  <tbody>
    @for (task of data(); track task) {
      <tr ngGridRow>
        <td ngGridCell>
          <input
            ngGridCellWidget
            aria-label="Select row {{ $index + 1 }}"
            type="checkbox"
            [(ngModel)]="task.selected"
          />
        </td>
        <td ngGridCell>${{ task.reward }}</td>
        <td ngGridCell>{{ task.target }}</td>
        <td ngGridCell>{{ task.rank }}</td>
        <td ngGridCell class="assignee-cell">
          <div
            type="button"
            ngGridCellWidget
            aria-label="edit hunter"
            widgetType="editable"
            (activated)="startEdit($event, task, assigneeInput)"
            (deactivated)="completeEdit($event, task)"
            #widget="ngGridCellWidget"
          >
            <span [class.hidden]="widget.isActivated()">{{ task.hunter }}</span>
            <input
              [class.hidden]="!widget.isActivated()"
              class="assignee-edit-input"
              [(ngModel)]="tempInput"
              #assigneeInput
            />
            <button
              tabindex="-1"
              aria-label="edit hunter"
              class="material-symbols-outlined assignee-edit-button"
              (click)="onClickEdit(widget, task, assigneeInput)"
              [class.hidden]="widget.isActivated()"
              translate="no"
            >
              edit
            </button>
          </div>
        </td>
      </tr>
    }
  </tbody>
</table>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P';

  --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background));
  --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.hidden {
  display: none;
}

button {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  outline: none;
}

input[type='checkbox'] {
  accent-color: var(--hot-pink);
  transform: scale(1.3);
  outline: none;
  cursor: pointer;
}

[ngGrid] {
  border-spacing: 0 0.5rem;
  display: table;
}

[ngGrid] th,
[ngGrid] td {
  padding: 0.5rem 0.75rem;
}

thead {
  background-color: var(--retro-button-color);
  color: var(--retro-button-text-color);
  box-shadow: var(--retro-elevated-shadow);
}

tbody [ngGridRow]:focus-within,
tbody [ngGridRow]:hover {
  background-color: var(--septenary-contrast);
}

[ngGridCell]:focus-within,
[ngGridCell]:hover {
  outline-offset: 4px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

.sort-button {
  display: flex;
  align-items: center;
  cursor: pointer;
  font-family: 'Press Start 2P';
  font-size: 1rem;
}

.assignee-cell [ngGridCellWidget] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
}

.assignee-edit-button {
  visibility: hidden;
  cursor: pointer;
}

.assignee-cell:focus-within .assignee-edit-button,
.assignee-cell:hover .assignee-edit-button {
  visibility: initial;
}

.assignee-edit-input {
  outline: none;
  border: none;
  color: var(--full-contrast);
  background-color: var(--page-background);
  font-size: 1rem;
  padding: 0.5rem;
}
```

Apply the [`ngGrid`](/api/aria/grid/Grid) directive to the table element, [`ngGridRow`](/api/aria/grid/GridRow) to each row, and [`ngGridCell`](/api/aria/grid/GridCell) to each cell.

### [Calendar grid](#calendar-grid)

Calendars are a common use case for grids. This example shows a month view where users navigate dates using arrow keys.

### TS

```
import {
  Component,
  computed,
  inject,
  signal,
  Signal,
  untracked,
  viewChildren,
  WritableSignal,
} from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MatDateFormats,
  provideNativeDateAdapter,
} from '@angular/material/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

const DAYS_PER_WEEK = 7;

interface CalendarCell<D = any> {
  displayName: string;
  ariaLabel: string;
  date: D;
  selected: WritableSignal<boolean>;
  day: number;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  providers: [provideNativeDateAdapter()],
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App<D> {
  private readonly _dayButtons = viewChildren(GridCellWidget);
  private readonly _dateAdapter = inject<DateAdapter<D>>(DateAdapter, {optional: true})!;
  private readonly _dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS, {optional: true})!;
  private readonly _firstWeekOffset = computed(() => {
    const firstDayOfMonth = this._dateAdapter.createDate(
      this._dateAdapter.getYear(this.viewMonth()),
      this._dateAdapter.getMonth(this.viewMonth()),
      1,
    );

    return (
      (DAYS_PER_WEEK +
        this._dateAdapter.getDayOfWeek(firstDayOfMonth) -
        this._dateAdapter.getFirstDayOfWeek()) %
      DAYS_PER_WEEK
    );
  });

  protected readonly monthYearLabel = computed(() =>
    this._dateAdapter
      .format(this.viewMonth(), this._dateFormats.display.monthYearLabel)
      .toLocaleUpperCase(),
  );

  protected readonly daysFromPrevMonth: Signal<number[]> = computed(() => {
    const prevMonthNumDays = this._dateAdapter.getNumDaysInMonth(
      this._dateAdapter.addCalendarMonths(this.viewMonth(), -1),
    );

    const days: number[] = [];
    for (let i = this._firstWeekOffset() - 1; i >= 0; i--) {
      days.push(prevMonthNumDays - i);
    }
    return days;
  });

  readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => {
    const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
    const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
    const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');

    const weekdays = longWeekdays.map((long, i) => {
      return {long, narrow: narrowWeekdays[i]};
    });
    return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
  });

  /** The current selected date. */
  readonly selectedDate: WritableSignal<D> = signal(this._dateAdapter.today());

  /** The current display month. */
  readonly viewMonth: WritableSignal<D> = signal(this.selectedDate());

  /** Calendar day cells. */
  readonly calendar = computed(() => {
    const month = this.viewMonth();

    const daysInMonth = this._dateAdapter.getNumDaysInMonth(month);
    const dateNames = this._dateAdapter.getDateNames();

    const calendar: CalendarCell[][] = [[]];
    for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) {
      if (cell == DAYS_PER_WEEK) {
        calendar.push([]);
        cell = 0;
      }
      const date = this._dateAdapter.createDate(
        this._dateAdapter.getYear(month),
        this._dateAdapter.getMonth(month),
        i + 1,
      );
      const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);

      calendar[calendar.length - 1].push({
        displayName: dateNames[i],
        ariaLabel,
        date,
        selected: signal(
          this._dateAdapter.compareDate(
            date,
            untracked(() => this.selectedDate()),
          ) === 0,
        ),
        day: i + 1,
      });
    }

    return calendar;
  });

  nextMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1));
  }

  prevMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1));
  }

  scrollDown(): void {
    this.nextMonth();
    setTimeout(() => this._dayButtons()[0]?.element.focus());
  }

  scrollUp(): void {
    this.prevMonth();
    setTimeout(() => this._dayButtons()[this._dayButtons().length - 1]?.element.focus());
  }

  onKeyDown(event: KeyboardEvent): void {
    const day = Number((event.target as Element).getAttribute('data-day'));
    if (!day) return;

    const viewMonthNumDays = this._dateAdapter.getNumDaysInMonth(this.viewMonth());
    if (day > 7 && day <= viewMonthNumDays - 7) return;

    const arrowLeft = event.key === 'ArrowLeft';
    const arrowRight = event.key === 'ArrowRight';
    const arrowUp = event.key === 'ArrowUp';
    const arrowDown = event.key === 'ArrowDown';

    if ((day === 1 && arrowLeft) || (day <= 7 && arrowUp)) {
      this.scrollUp();
    }

    if ((day === viewMonthNumDays && arrowRight) || (day > viewMonthNumDays - 7 && arrowDown)) {
      this.scrollDown();
    }
  }
}
```

### HTML

```
<div class="calendar basic-calendar">
  <div class="calendar-header">
    <button class="month-control" aria-label="previous month" (click)="prevMonth()">
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">chevron_left</span>
    </button>
    <h3>{{ monthYearLabel() }}</h3>
    <button class="month-control" aria-label="next month" (click)="nextMonth()">
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">chevron_right</span>
    </button>
  </div>

  <table
    ngGrid
    colWrap="continuous"
    rowWrap="nowrap"
    [enableSelection]="true"
    [softDisabled]="false"
    selectionMode="explicit"
    (keydown)="onKeyDown($event)"
  >
    <thead>
      <tr>
        @for (day of weekdays(); track day.long) {
          <th scope="col">
            <span class="visually-hidden">{{ day.long }}</span>
            <span aria-hidden="true">{{ day.narrow }}</span>
          </th>
        }
      </tr>
    </thead>

    @for (week of calendar(); track week) {
      <tr ngGridRow>
        @if ($first) {
          @for (day of daysFromPrevMonth(); track day) {
            <td ngGridCell disabled>{{ day }}</td>
          }
        }

        @for (day of week; track day) {
          <td ngGridCell [(selected)]="day.selected">
            <button ngGridCellWidget [attr.aria-label]="day.ariaLabel" [attr.data-day]="day.day">
              {{ day.displayName }}
            </button>
          </td>
        }

        @if ($last && week.length < 7) {
          @for (day of [].constructor(7 - week.length); track $index) {
            <td ngGridCell disabled>{{ $index + 1 }}</td>
          }
        }
      </tr>
    }
  </table>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.calendar {
  display: flex;
  flex-direction: column;
  background-color: var(--septenary-contrast);
  padding: 0.5rem;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-block-end: 0.5rem;
}

.calendar-header h3 {
  margin: 0;
  font-size: 1.2rem;
}

button {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  outline: none;
}

button:hover,
button:focus {
  background-color: var(--senary-contrast);
}

button:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

.visually-hidden {
  clip: rect(1px, 1px, 1px, 1px);
  height: 1px;
  width: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

.month-control {
  width: 45px;
  height: 45px;
  cursor: pointer;
}

[ngGrid] {
  display: table;
  border-spacing: 0;
}

[ngGridCell] {
  width: 50px;
  height: 50px;
  text-align: center;
  vertical-align: middle;
}

[ngGridCell][aria-selected='true'] > button[ngGridCellWidget] {
  background-color: var(--electric-violet);
  color: var(--octonary-contrast);
}

[ngGridCell][aria-disabled='true'] {
  color: var(--senary-contrast);
}

thead {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

button[ngGridCellWidget] {
  width: 45px;
  height: 45px;
  cursor: pointer;
}
```

### TS

```
import {
  Component,
  computed,
  inject,
  signal,
  Signal,
  untracked,
  viewChildren,
  WritableSignal,
} from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MatDateFormats,
  provideNativeDateAdapter,
} from '@angular/material/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

const DAYS_PER_WEEK = 7;

interface CalendarCell<D = any> {
  displayName: string;
  ariaLabel: string;
  date: D;
  selected: WritableSignal<boolean>;
  day: number;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  providers: [provideNativeDateAdapter()],
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App<D> {
  private readonly _dayButtons = viewChildren(GridCellWidget);
  private readonly _dateAdapter = inject<DateAdapter<D>>(DateAdapter, {optional: true})!;
  private readonly _dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS, {optional: true})!;
  private readonly _firstWeekOffset = computed(() => {
    const firstDayOfMonth = this._dateAdapter.createDate(
      this._dateAdapter.getYear(this.viewMonth()),
      this._dateAdapter.getMonth(this.viewMonth()),
      1,
    );

    return (
      (DAYS_PER_WEEK +
        this._dateAdapter.getDayOfWeek(firstDayOfMonth) -
        this._dateAdapter.getFirstDayOfWeek()) %
      DAYS_PER_WEEK
    );
  });

  protected readonly monthYearLabel = computed(() =>
    this._dateAdapter
      .format(this.viewMonth(), this._dateFormats.display.monthYearLabel)
      .toLocaleUpperCase(),
  );

  protected readonly daysFromPrevMonth: Signal<number[]> = computed(() => {
    const prevMonthNumDays = this._dateAdapter.getNumDaysInMonth(
      this._dateAdapter.addCalendarMonths(this.viewMonth(), -1),
    );

    const days: number[] = [];
    for (let i = this._firstWeekOffset() - 1; i >= 0; i--) {
      days.push(prevMonthNumDays - i);
    }
    return days;
  });

  readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => {
    const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
    const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
    const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');

    const weekdays = longWeekdays.map((long, i) => {
      return {long, narrow: narrowWeekdays[i]};
    });
    return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
  });

  /** The current selected date. */
  readonly selectedDate: WritableSignal<D> = signal(this._dateAdapter.today());

  /** The current display month. */
  readonly viewMonth: WritableSignal<D> = signal(this.selectedDate());

  /** Calendar day cells. */
  readonly calendar = computed(() => {
    const month = this.viewMonth();

    const daysInMonth = this._dateAdapter.getNumDaysInMonth(month);
    const dateNames = this._dateAdapter.getDateNames();

    const calendar: CalendarCell[][] = [[]];
    for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) {
      if (cell == DAYS_PER_WEEK) {
        calendar.push([]);
        cell = 0;
      }
      const date = this._dateAdapter.createDate(
        this._dateAdapter.getYear(month),
        this._dateAdapter.getMonth(month),
        i + 1,
      );
      const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);

      calendar[calendar.length - 1].push({
        displayName: dateNames[i],
        ariaLabel,
        date,
        selected: signal(
          this._dateAdapter.compareDate(
            date,
            untracked(() => this.selectedDate()),
          ) === 0,
        ),
        day: i + 1,
      });
    }

    return calendar;
  });

  nextMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1));
  }

  prevMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1));
  }

  scrollDown(): void {
    this.nextMonth();
    setTimeout(() => this._dayButtons()[0]?.element.focus());
  }

  scrollUp(): void {
    this.prevMonth();
    setTimeout(() => this._dayButtons()[this._dayButtons().length - 1]?.element.focus());
  }

  onKeyDown(event: KeyboardEvent): void {
    const day = Number((event.target as Element).getAttribute('data-day'));
    if (!day) return;

    const viewMonthNumDays = this._dateAdapter.getNumDaysInMonth(this.viewMonth());
    if (day > 7 && day <= viewMonthNumDays - 7) return;

    const arrowLeft = event.key === 'ArrowLeft';
    const arrowRight = event.key === 'ArrowRight';
    const arrowUp = event.key === 'ArrowUp';
    const arrowDown = event.key === 'ArrowDown';

    if ((day === 1 && arrowLeft) || (day <= 7 && arrowUp)) {
      this.scrollUp();
    }

    if ((day === viewMonthNumDays && arrowRight) || (day > viewMonthNumDays - 7 && arrowDown)) {
      this.scrollDown();
    }
  }
}
```

### HTML

```
<div class="calendar material-calendar">
  <div class="calendar-header">
    <button class="month-control" aria-label="previous month" (click)="prevMonth()">
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">chevron_left</span>
    </button>
    <h3>{{ monthYearLabel() }}</h3>
    <button class="month-control" aria-label="next month" (click)="nextMonth()">
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">chevron_right</span>
    </button>
  </div>

  <table
    ngGrid
    colWrap="continuous"
    rowWrap="nowrap"
    [enableSelection]="true"
    [softDisabled]="false"
    selectionMode="explicit"
    (keydown)="onKeyDown($event)"
  >
    <thead>
      <tr>
        @for (day of weekdays(); track day.long) {
          <th scope="col">
            <span class="visually-hidden">{{ day.long }}</span>
            <span aria-hidden="true">{{ day.narrow }}</span>
          </th>
        }
      </tr>
    </thead>

    @for (week of calendar(); track week) {
      <tr ngGridRow>
        @if ($first) {
          @for (day of daysFromPrevMonth(); track day) {
            <td ngGridCell disabled>{{ day }}</td>
          }
        }

        @for (day of week; track day) {
          <td ngGridCell [(selected)]="day.selected">
            <button ngGridCellWidget [attr.aria-label]="day.ariaLabel" [attr.data-day]="day.day">
              {{ day.displayName }}
            </button>
          </td>
        }

        @if ($last && week.length < 7) {
          @for (day of [].constructor(7 - week.length); track $index) {
            <td ngGridCell disabled>{{ $index + 1 }}</td>
          }
        }
      </tr>
    }
  </table>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.calendar {
  display: flex;
  flex-direction: column;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-block-end: 0.5rem;
}

.calendar-header h3 {
  margin: 0;
  font-size: 1.2rem;
}

button {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  outline: none;
  border-radius: 50%;
}

button:hover,
button:focus {
  background-color: var(--senary-contrast);
}

button:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--bright-blue) 60%, transparent);
}

.visually-hidden {
  clip: rect(1px, 1px, 1px, 1px);
  height: 1px;
  width: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

.month-control {
  width: 45px;
  height: 45px;
  cursor: pointer;
}

[ngGrid] {
  display: table;
  border-spacing: 0;
}

[ngGridCell] {
  width: 50px;
  height: 50px;
  text-align: center;
  vertical-align: middle;
}

[ngGridCell][aria-selected='true'] > button[ngGridCellWidget] {
  background-color: var(--indigo-blue);
  color: var(--octonary-contrast);
}

[ngGridCell][aria-disabled='true'] {
  color: var(--senary-contrast);
}

thead {
  color: var(--secondary-contrast);
}

button[ngGridCellWidget] {
  width: 45px;
  height: 45px;
  cursor: pointer;
}
```

### TS

```
import {
  Component,
  computed,
  inject,
  signal,
  Signal,
  untracked,
  viewChildren,
  WritableSignal,
} from '@angular/core';
import {
  DateAdapter,
  MAT_DATE_FORMATS,
  MatDateFormats,
  provideNativeDateAdapter,
} from '@angular/material/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

const DAYS_PER_WEEK = 7;

interface CalendarCell<D = any> {
  displayName: string;
  ariaLabel: string;
  date: D;
  selected: WritableSignal<boolean>;
  day: number;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  providers: [provideNativeDateAdapter()],
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App<D> {
  private readonly _dayButtons = viewChildren(GridCellWidget);
  private readonly _dateAdapter = inject<DateAdapter<D>>(DateAdapter, {optional: true})!;
  private readonly _dateFormats = inject<MatDateFormats>(MAT_DATE_FORMATS, {optional: true})!;
  private readonly _firstWeekOffset = computed(() => {
    const firstDayOfMonth = this._dateAdapter.createDate(
      this._dateAdapter.getYear(this.viewMonth()),
      this._dateAdapter.getMonth(this.viewMonth()),
      1,
    );

    return (
      (DAYS_PER_WEEK +
        this._dateAdapter.getDayOfWeek(firstDayOfMonth) -
        this._dateAdapter.getFirstDayOfWeek()) %
      DAYS_PER_WEEK
    );
  });

  protected readonly monthYearLabel = computed(() =>
    this._dateAdapter
      .format(this.viewMonth(), this._dateFormats.display.monthYearLabel)
      .toLocaleUpperCase(),
  );

  protected readonly daysFromPrevMonth: Signal<number[]> = computed(() => {
    const prevMonthNumDays = this._dateAdapter.getNumDaysInMonth(
      this._dateAdapter.addCalendarMonths(this.viewMonth(), -1),
    );

    const days: number[] = [];
    for (let i = this._firstWeekOffset() - 1; i >= 0; i--) {
      days.push(prevMonthNumDays - i);
    }
    return days;
  });

  readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => {
    const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek();
    const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow');
    const longWeekdays = this._dateAdapter.getDayOfWeekNames('long');

    const weekdays = longWeekdays.map((long, i) => {
      return {long, narrow: narrowWeekdays[i]};
    });
    return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek));
  });

  /** The current selected date. */
  readonly selectedDate: WritableSignal<D> = signal(this._dateAdapter.today());

  /** The current display month. */
  readonly viewMonth: WritableSignal<D> = signal(this.selectedDate());

  /** Calendar day cells. */
  readonly calendar = computed(() => {
    const month = this.viewMonth();

    const daysInMonth = this._dateAdapter.getNumDaysInMonth(month);
    const dateNames = this._dateAdapter.getDateNames();

    const calendar: CalendarCell[][] = [[]];
    for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) {
      if (cell == DAYS_PER_WEEK) {
        calendar.push([]);
        cell = 0;
      }
      const date = this._dateAdapter.createDate(
        this._dateAdapter.getYear(month),
        this._dateAdapter.getMonth(month),
        i + 1,
      );
      const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel);

      calendar[calendar.length - 1].push({
        displayName: dateNames[i],
        ariaLabel,
        date,
        selected: signal(
          this._dateAdapter.compareDate(
            date,
            untracked(() => this.selectedDate()),
          ) === 0,
        ),
        day: i + 1,
      });
    }

    return calendar;
  });

  nextMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1));
  }

  prevMonth(): void {
    this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1));
  }

  scrollDown(): void {
    this.nextMonth();
    setTimeout(() => this._dayButtons()[0]?.element.focus());
  }

  scrollUp(): void {
    this.prevMonth();
    setTimeout(() => this._dayButtons()[this._dayButtons().length - 1]?.element.focus());
  }

  onKeyDown(event: KeyboardEvent): void {
    const day = Number((event.target as Element).getAttribute('data-day'));
    if (!day) return;

    const viewMonthNumDays = this._dateAdapter.getNumDaysInMonth(this.viewMonth());
    if (day > 7 && day <= viewMonthNumDays - 7) return;

    const arrowLeft = event.key === 'ArrowLeft';
    const arrowRight = event.key === 'ArrowRight';
    const arrowUp = event.key === 'ArrowUp';
    const arrowDown = event.key === 'ArrowDown';

    if ((day === 1 && arrowLeft) || (day <= 7 && arrowUp)) {
      this.scrollUp();
    }

    if ((day === viewMonthNumDays && arrowRight) || (day > viewMonthNumDays - 7 && arrowDown)) {
      this.scrollDown();
    }
  }
}
```

### HTML

```
<div class="calendar retro-calendar">
  <div class="calendar-header">
    <button class="month-control" aria-label="previous month" (click)="prevMonth()">
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">chevron_left</span>
    </button>
    <h3>{{ monthYearLabel() }}</h3>
    <button class="month-control" aria-label="next month" (click)="nextMonth()">
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">chevron_right</span>
    </button>
  </div>

  <table
    ngGrid
    colWrap="continuous"
    rowWrap="nowrap"
    [enableSelection]="true"
    [softDisabled]="false"
    selectionMode="explicit"
    (keydown)="onKeyDown($event)"
  >
    <thead>
      <tr>
        @for (day of weekdays(); track day.long) {
          <th scope="col">
            <span class="visually-hidden">{{ day.long }}</span>
            <span aria-hidden="true">{{ day.narrow }}</span>
          </th>
        }
      </tr>
    </thead>

    @for (week of calendar(); track week) {
      <tr ngGridRow>
        @if ($first) {
          @for (day of daysFromPrevMonth(); track day) {
            <td ngGridCell disabled>{{ day }}</td>
          }
        }

        @for (day of week; track day) {
          <td ngGridCell [(selected)]="day.selected">
            <button ngGridCellWidget [attr.aria-label]="day.ariaLabel" [attr.data-day]="day.day">
              {{ day.displayName }}
            </button>
          </td>
        }

        @if ($last && week.length < 7) {
          @for (day of [].constructor(7 - week.length); track $index) {
            <td ngGridCell disabled>{{ $index + 1 }}</td>
          }
        }
      </tr>
    }
  </table>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P';

  --retro-button-color: color-mix(in srgb, var(--always-pink) 90%, var(--page-background));
  --retro-button-text-color: color-mix(in srgb, var(--always-pink) 10%, white);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.calendar {
  display: flex;
  flex-direction: column;
  background-color: var(--septenary-contrast);
  padding: 0.5rem;
  box-shadow: var(--retro-flat-shadow);
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-block-end: 0.5rem;
}

.calendar-header h3 {
  margin: 0;
  font-size: 1.2rem;
}

button {
  font-family: 'Press Start 2P';
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  outline: none;
}

button:hover,
button:focus {
  background-color: var(--senary-contrast);
}

button:focus {
  outline-offset: 4px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 90%, transparent);
}

.visually-hidden {
  clip: rect(1px, 1px, 1px, 1px);
  height: 1px;
  width: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
}

.month-control {
  width: 45px;
  height: 45px;
  cursor: pointer;
}

[ngGrid] {
  display: table;
  border-spacing: 0;
}

[ngGridCell] {
  width: 50px;
  height: 50px;
  text-align: center;
  vertical-align: middle;
}

[ngGridCell][aria-selected='true'] > button[ngGridCellWidget] {
  background-color: var(--retro-button-color);
  color: var(--retro-button-text-color);
  box-shadow: var(--retro-clickable-shadow);
}

[ngGridCell][aria-disabled='true'] {
  color: var(--senary-contrast);
}

thead {
  background-image: var(--orange-to-pink-vertical-gradient);
  background-clip: text;
  color: transparent;
}

button[ngGridCellWidget] {
  width: 45px;
  height: 45px;
  cursor: pointer;
}
```

Users can activate a date by pressing Enter or Space when focused on a cell.

### [Layout grid](#layout-grid)

Use a layout grid to group interactive elements and reduce tab stops. This example shows a grid of pill buttons.

### TS

```
import {Component, signal} from '@angular/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App {
  tags = signal(['Unleash', 'Your', 'Creativity', 'With', 'Angular', 'Aria']);

  removeTag(index: number) {
    this.tags.update((tags) => [...tags.slice(0, index), ...tags.slice(index + 1)]);
  }
}
```

### HTML

```
<div ngGrid colWrap="continuous" class="basic-pill-list">
  @for (tag of tags(); track $index) {
    <div ngGridRow>
      <span ngGridCell>#{{ tag }}</span>
      <span ngGridCell>
        <button
          ngGridCellWidget
          aria-label="remove tag"
          class="material-symbols-outlined"
          (click)="removeTag($index)"
          translate="no"
        >
          close
        </button>
      </span>
    </div>
  }
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngGrid] {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  max-width: 400px;
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

[ngGridRow] {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px dotted var(--senary-contrast);
  padding: 0 0.25rem 0 0.75rem;
}

[ngGridRow]:focus-within,
[ngGridRow]:hover {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--vivid-pink) 80%, transparent);
}

[ngGridRow]:has(button[ngGridCellWidget]:focus),
[ngGridRow]:has(button[ngGridCellWidget]:hover) {
  outline: none;
}

[ngGridCell] {
  display: flex;
  outline: none;
}

button[ngGridCellWidget] {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  font-size: 1.2rem;
  width: 1.5rem;
  height: 1.5rem;
  margin: 0.25rem;
  border-radius: 50%;
  cursor: pointer;
}

button[ngGridCellWidget]:focus,
button[ngGridCellWidget]:hover {
  outline: 1px solid color-mix(in srgb, var(--vivid-pink) 80%, transparent);
}
```

### TS

```
import {Component, signal} from '@angular/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App {
  tags = signal(['Unleash', 'Your', 'Creativity', 'With', 'Angular', 'Aria']);

  removeTag(index: number) {
    this.tags.update((tags) => [...tags.slice(0, index), ...tags.slice(index + 1)]);
  }
}
```

### HTML

```
<div ngGrid colWrap="continuous" class="material-pill-list">
  @for (tag of tags(); track $index) {
    <div ngGridRow>
      <span ngGridCell>{{ tag }}</span>
      <span ngGridCell>
        <button
          ngGridCellWidget
          aria-label="remove tag"
          class="material-symbols-outlined"
          (click)="removeTag($index)"
          translate="no"
        >
          close
        </button>
      </span>
    </div>
  }
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngGrid] {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  max-width: 400px;
}

[ngGridRow] {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--senary-contrast);
  border-radius: 0.5rem;
  padding: 0 0.25rem 0 0.75rem;
}

[ngGridRow]:focus-within,
[ngGridRow]:hover {
  background-color: var(--senary-contrast);
}

[ngGridRow]:has(button[ngGridCellWidget]:focus),
[ngGridRow]:has(button[ngGridCellWidget]:hover) {
  background-color: initial;
}

[ngGridCell] {
  display: flex;
  outline: none;
}

button[ngGridCellWidget] {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  font-size: 1.2rem;
  width: 1.5rem;
  height: 1.5rem;
  margin: 0.25rem;
  border-radius: 50%;
  cursor: pointer;
}

button[ngGridCellWidget]:focus,
button[ngGridCellWidget]:hover {
  outline: none;
  background-color: var(--septenary-contrast);
}
```

### TS

```
import {Component, signal} from '@angular/core';
import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Grid, GridRow, GridCell, GridCellWidget],
})
export class App {
  tags = signal(['Unleash', 'Your', 'Creativity', 'With', 'Angular', 'Aria']);

  removeTag(index: number) {
    this.tags.update((tags) => [...tags.slice(0, index), ...tags.slice(index + 1)]);
  }
}
```

### HTML

```
<div ngGrid colWrap="continuous" class="retro-pill-list">
  @for (tag of tags(); track $index) {
    <div ngGridRow>
      <span ngGridCell>#{{ tag }}</span>
      <span ngGridCell>
        <button
          ngGridCellWidget
          aria-label="remove tag"
          class="material-symbols-outlined"
          (click)="removeTag($index)"
          translate="no"
        >
          close
        </button>
      </span>
    </div>
  }
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P';

  --retro-button-color: color-mix(in srgb, var(--always-pink) 90%, var(--page-background));
  --retro-button-text-color: color-mix(in srgb, var(--always-pink) 10%, white);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngGrid] {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
  max-width: 400px;
}

[ngGridRow] {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.25rem 0 0.75rem;
  color: var(--retro-button-text-color);
  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
}

[ngGridRow]:focus-within,
[ngGridRow]:hover {
  outline-offset: 4px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 90%, transparent);
}

[ngGridRow]:has(button[ngGridCellWidget]:focus),
[ngGridRow]:has(button[ngGridCellWidget]:hover) {
  outline: none;
}

[ngGridCell] {
  display: flex;
  outline: none;
}

button[ngGridCellWidget] {
  border: unset;
  padding: unset;
  color: unset;
  background: unset;
  font-size: 1.5rem;
  margin: 0.25rem;
  cursor: pointer;
}

button[ngGridCellWidget]:focus,
button[ngGridCellWidget]:hover {
  outline-offset: 8px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 90%, transparent);
}
```

Instead of tabbing through each button, users navigate with arrow keys and only one button receives tab focus.

### [Selection and focus modes](#selection-and-focus-modes)

Enable selection with `[enableSelection]="true"` and configure how focus and selection interact.

```
<table
  ngGrid
  [enableSelection]="true"
  [selectionMode]="'explicit'"
  [multi]="true"
  [focusMode]="'roving'"
>
  <tr ngGridRow>
    <td ngGridCell>Cell 1</td>
    <td ngGridCell>Cell 2</td>
  </tr>
</table>
```

**Selection modes:**

* `follow`: Focused cell is automatically selected
* `explicit`: Users select cells with Space or click

**Focus modes:**

* `roving`: Focus moves to cells using `tabindex` (better for simple grids)
* `activedescendant`: Focus stays on grid container, `aria-activedescendant` indicates active cell (better for virtual scrolling)

## [Testing](#testing)

Angular Aria provides component harnesses for testing grid components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {GridHarness} from '@angular/aria/grid/testing';
import {MyGridComponent} from './my-grid'; // Your component

describe('MyGridComponent', () => {
  let fixture: ComponentFixture<MyGridComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyGridComponent],
    });

    fixture = TestBed.createComponent(MyGridComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should read cell values and focus cells', async () => {
    const grid = await loader.getHarness(GridHarness);

    // Get all cells text in a 2D array organized by rows
    const cellTexts = await grid.getCellTextByIndex();
    expect(cellTexts).toEqual([
      ['Cell 1.1', 'Cell 1.2'],
      ['Cell 2.1', 'Cell 2.2'],
    ]);

    // Get a specific cell by text
    const cells = await grid.getCells({text: 'Cell 1.1'});
    expect(cells.length).toBe(1);
    const cell = cells[0];

    // Verify cell state
    expect(await cell.isSelected()).toBe(true);
    expect(await cell.isActive()).toBe(true);

    // Focus the cell
    await cell.focus();
    expect(await cell.isFocused()).toBe(true);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Grid`](/api/aria/grid/Grid)
* [`GridRow`](/api/aria/grid/GridRow)
* [`GridCell`](/api/aria/grid/GridCell)
* [`GridCellWidget`](/api/aria/grid/GridCellWidget)

----
url: https://angular.dev/api/core/linkedSignal
----

```
function linkedSignal<D>(
  computation: () => D,
  options?:
    | {
        equal?: ValueEqualityFn<NoInfer<D>> | undefined;
        debugName?: string | undefined;
        set?:
          | ((value: NoInfer<D>, rawSet: (value: NoInfer<D>) => void) => void)
          | undefined;
      }
    | undefined,
): WritableSignal<D>;
function linkedSignal<S, D>(options: {
  source: () => S;
  computation: (
    source: NoInfer<S>,
    previous?: { source: NoInfer<S>; value: NoInfer<D> } | undefined,
  ) => D;
  equal?: ValueEqualityFn<NoInfer<D>> | undefined;
  debugName?: string | undefined;
  set?:
    | ((value: NoInfer<D>, rawSet: (value: NoInfer<D>) => void) => void)
    | undefined;
}): WritableSignal<D>;
```

Creates a writable signal whose value is initialized and reset by the linked, reactive computation.

@paramcomputation`() => D`

@paramoptions`{ equal?: ValueEqualityFn<NoInfer<D>> | undefined; debugName?: string | undefined; set?: ((value: NoInfer<D>, rawSet: (value: NoInfer<D>) => void) => void) | undefined; } | undefined`

@returns`WritableSignal<D>`

Creates a writable signal whose value is initialized and reset by the linked, reactive computation. This is an advanced API form where the computation has access to the previous value of the signal and the computation result.

Note: The computation is reactive, meaning the linked signal will automatically update whenever any of the signals used within the computation change.

@paramoptions`{ source: () => S; computation: (source: NoInfer<S>, previous?: { source: NoInfer<S>; value: NoInfer<D>; } | undefined) => D; equal?: ValueEqualityFn<NoInfer<D>> | undefined; debugName?: string | undefined; set?: ((value: NoInfer<D>, rawSet: (value: NoInfer<D>) => void) => void) | undefined; }`

@returns`WritableSignal<D>`

Jump to details

----
url: https://angular.dev/api/core/testing/DeferBlockBehavior
----

```
    
      enum DeferBlockBehavior {}
    
    
```

Manual triggering mode for defer blocks. Provides control over when defer blocks render and which state they render.

Playthrough mode for defer blocks. This mode behaves like defer blocks would in a browser. This is the default behavior in test environments.

Jump to details

----
url: https://angular.dev/api/router/upgrade/locationSyncBootstrapListener
----

```
function locationSyncBootstrapListener(): () => void;
```

@returns`() => void`

Jump to details

----
url: https://angular.dev/errors/NG0501
----

This error is similar to the [error NG0500](errors/NG0500), but it means Angular's hydration process expected more sibling nodes at a specific spot in the DOM and did not find them. Check the [error NG0500](errors/NG0500) page for causes related to direct DOM manipulation.

## [A common cause: conditional content that differs between server and client](#a-common-cause-conditional-content-that-differs-between-server-and-client)

Besides direct DOM manipulation, this error often happens when content inside `@if` or `@for` renders differently on the server than it does when the client hydrates. If the server rendered a condition as `true` (or a list with more items), but the client sees it as `false` (or a shorter list) when hydration runs, the client ends up expecting fewer sibling nodes than the server actually sent. Hydration then fails while walking through the siblings.

A common way this happens is when a value comes from an [`input()`](/api/core/input), but instead of using that input directly (or through [`computed()`](/api/core/computed)), it gets copied into a plain [`signal()`](/api/core/signal) one time inside `ngOnInit()` or the constructor:

```
@Component({
  selector: 'app-example',
  template: `
    @if (visibleItems().length) {
      <ul>
        @for (item of visibleItems(); track item) {
          <li>{{ item }}</li>
        }
      </ul>
    }
  `,
})
export class Example {
  items = input<string[]>();

  // This only runs once. If `items()` is not ready yet at the exact
  // moment `ngOnInit` runs, this signal can end up holding a different
  // value on the server than it does on the client. That means a
  // different number of DOM nodes gets rendered on each side, which
  // causes a hydration mismatch.
  visibleItems = signal<string[]>([]);

  ngOnInit() {
    this.visibleItems.set(this.items() ?? []);
  }
}
```

The fix is to derive the value instead of copying it once. That way every render, including the very first one, always matches the input:

```
export class Example {
  items = input<string[]>();

  visibleItems = computed(() => this.items() ?? []);
}
```

If you also need to let users override that value locally, for example a "show more" button that reveals extra items, use [`linkedSignal()`](guide/signals/linked-signal) instead of a plain [`signal()`](/api/core/signal) set from a lifecycle hook.

## [Debugging the error](#debugging-the-error)

See the [error NG0500](errors/NG0500) for tips on debugging causes related to direct DOM manipulation.

This error alone will not tell you which component caused it. The [Angular DevTools browser extension](tools/devtools) can help: it can highlight the exact component where the hydration mismatch happened. Once you find it, look at how it computes any value used inside an `@if` or `@for` in its template. Check whether that value comes straight from an [`input()`](/api/core/input) (through a template binding or [`computed()`](/api/core/computed)), or whether it gets copied into a signal inside a lifecycle hook like shown above.

----
url: https://angular.dev/tools/cli/setup-local
----

This guide explains how to set up your environment for Angular development using the [Angular CLI](cli "CLI command reference"). It includes information about installing the CLI, creating an initial workspace and starter app, and running that app locally to verify your setup.

### Try Angular without local setup

If you are new to Angular, you might want to start with [Try it now!](tutorials/learn-angular), which introduces the essentials of Angular in your browser. This standalone tutorial takes advantage of the interactive [StackBlitz](https://stackblitz.com) environment for online development. You don't need to set up your local environment until you're ready.

## [Before you start](#before-you-start)

To use Angular CLI, you should be familiar with the following:

You should also be familiar with usage of command line interface (CLI) tools and have a general understanding of command shells. Knowledge of [TypeScript](https://www.typescriptlang.org) is helpful, but not required.

## [Dependencies](#dependencies)

To install Angular CLI on your local system, you need to install [Node.js](https://nodejs.org/). Angular CLI uses Node and its associated package manager, npm, to install and run JavaScript tools outside the browser.

[Download and install Node.js](https://nodejs.org/en/download), which will include the `npm` CLI as well. Angular requires an [active LTS or maintenance LTS](https://nodejs.org/en/about/previous-releases) version of Node.js. See [Angular's version compatibility](reference/versions) guide for more information.

## [Install the Angular CLI](#install-the-angular-cli)

To install the Angular CLI, open a terminal window and run the following command:

### npm

```
npm install -g @angular/cli
```

### pnpm

```
pnpm install -g @angular/cli
```

### yarn

```
yarn global add @angular/cli
```

### bun

```
bun install -g @angular/cli
```

### [Powershell execution policy](#powershell-execution-policy)

On Windows client computers, the execution of PowerShell scripts is disabled by default, so the above command may fail with an error. To allow the execution of PowerShell scripts, which is needed for npm global binaries, you must set the following [execution policy](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_execution_policies):

```
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
```

Carefully read the message displayed after executing the command and follow the instructions. Make sure you understand the implications of setting an execution policy.

### [Unix permissions](#unix-permissions)

On some Unix-like setups, global scripts may be owned by the root user, so to the above command may fail with a permission error. Run with `sudo` to execute the command as the root user and enter your password when prompted:

### npm

```
sudo npm install -g @angular/cli
```

### pnpm

```
sudo pnpm install -g @angular/cli
```

### yarn

```
sudo yarn global add @angular/cli
```

### bun

```
sudo bun install -g @angular/cli
```

Make sure you understand the implications of running commands as root.

## [Create a workspace and initial application](#create-a-workspace-and-initial-application)

You develop apps in the context of an Angular **workspace**.

To create a new workspace and initial starter app, run the CLI command `ng new` and provide the name `my-app`, as shown here, then answer prompts about features to include:

```
ng new my-app
```

The Angular CLI installs the necessary Angular npm packages and other dependencies. This can take a few minutes.

The CLI creates a new workspace and a small welcome app in a new directory with the same name as the workspace, ready to run. Navigate to the new directory so subsequent commands use this workspace.

```
cd my-app
```

## [Run the application](#run-the-application)

The Angular CLI includes a development server, for you to build and serve your app locally. Run the following command:

```
ng serve --open
```

The `ng serve` command launches the server, watches your files, as well as rebuilds the app and reloads the browser as you make changes to those files.

The `--open` (or just `-o`) option automatically opens your browser to `http://localhost:4200/` to view the generated application.

## [Workspaces and project files](#workspaces-and-project-files)

The [`ng new`](cli/new) command creates an [Angular workspace](reference/configs/workspace-config) folder and generates a new application inside it. A workspace can contain multiple applications and libraries. The initial application created by the [`ng new`](cli/new) command is at the root directory of the workspace. When you generate an additional application or library in an existing workspace, it goes into a `projects/` subfolder by default.

A newly generated application contains the source files for a root component and template. Each application has a `src` folder that contains its components, data, and assets.

You can edit the generated files directly, or add to and modify them using CLI commands. Use the [`ng generate`](cli/generate) command to add new files for additional components, directives, pipes, services, and more. Commands such as [`ng add`](cli/add) and [`ng generate`](cli/generate), which create or operate on applications and libraries, must be executed from within a workspace. By contrast, commands such as `ng new` must be executed *outside* a workspace because they will create a new one.

## [Next steps](#next-steps)

* Learn more about the [file structure](reference/configs/file-structure) and [configuration](reference/configs/workspace-config) of the generated workspace.

* Test your new application with [`ng test`](cli/test).

* Generate boilerplate like components, directives, and pipes with [`ng generate`](cli/generate).

* Deploy your new application and make it available to real users with [`ng deploy`](cli/deploy).

* Set up and run end-to-end tests of your application with [`ng e2e`](cli/e2e).

----
url: https://angular.dev/api/animations/AnimationKeyframesSequenceMetadata
----

```
    
      interface AnimationKeyframesSequenceMetadata extends AnimationMetadata {}
    
    
```

An array of animation styles.

Jump to details

----
url: https://angular.dev/api/core/contentChildren
----

```
    
      
    
    
```

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ descendants?: boolean | undefined; read?: undefined; debugName?: string | undefined; } | undefined`

@returns`Signal<readonly LocatorT[]>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ descendants?: boolean | undefined; read: ProviderToken<ReadT>; debugName?: string | undefined; }`

@returns`Signal<readonly ReadT[]>`

## [Usage Notes](#usage-notes)

Create a children query in your component by declaring a class field and initializing it with the [`contentChildren()`](/api/core/contentChildren) function.

```
@Component({...})
export class TestComponent {
  headerEl = contentChildren<ElementRef>('h');   // Signal<ReadonlyArray<ElementRef>>
}
```

Note: By default `descendants` is `false` which means the query will not traverse all descendants in the same template.

Jump to details

----
url: https://angular.dev/api/forms/FormResetEvent
----

```
    
      class FormResetEvent extends ControlEvent {}
    
    
```

@paramsource`AbstractControl<any, any, any>`

@returns`FormResetEvent`

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/app-shell
----

The [App shell pattern](https://developer.chrome.com/blog/app-shell) is a way to render a portion of your application using a route at build time. It can improve the user experience by quickly launching a static rendered page (a skeleton common to all pages) while the browser downloads the full client version and switches to it automatically after the code loads.

This gives users a meaningful first paint of your application that appears quickly because the browser can render the HTML and CSS without the need to initialize any JavaScript.

1. ### [Prepare the application](#prepare-the-application)

   Do this with the following Angular CLI command:

   ```
   ng new my-app
   ```

   For an existing application, you have to manually add the [`Router`](/api/router/Router) and defining a `<router-outlet>` within your application.

2. ### [Create the application shell](#create-the-application-shell)

   Use the Angular CLI to automatically create the application shell.

   ```
   ng generate app-shell
   ```

   For more information about this command, see [App shell command](cli/generate/app-shell).

   The command updates the application code and adds extra files to the project structure.

   ```
   src
   ├── app
   │ ├── app.config.server.ts # server application configuration
   │ └── app-shell # app-shell component
   │   ├── app-shell.component.html
   │   ├── app-shell.component.scss
   │   ├── app-shell.component.spec.ts
   │   └── app-shell.component.ts
   └── main.server.ts # main server application bootstrapping
   ```

3. ### [Verify the application is built with the shell content](#verify-the-application-is-built-with-the-shell-content)

   ```
   ng build --configuration=development
   ```

   Or to use the production configuration.

   ```
   ng build
   ```

   To verify the build output, open `dist/my-app/browser/index.html`. Look for default text `app-shell works!` to show that the application shell route was rendered as part of the output.

----
url: https://angular.dev/api/aria/menu/MenuItem
----

## [API](/api/aria/menu/MenuItem#api)

```
    
      class MenuItem<V> implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

The unique ID of the menu item.

The value of the menu item.

Whether the menu item is disabled.

The search term associated with the menu item.

The role of the menu item.

A reference to the parent menu or menubar.

The submenu associated with the menu item.

Whether the menu item is active.

Whether the menu is expanded.

Whether the menu item has a popup.

@returns`void`

@returns`void`

Opens the submenu focusing on the first menu item.

@returns`void`

Closes the submenu.

@returns`void`

## [Description](/api/aria/menu/MenuItem#description)

An item in a Menu.

`ngMenuItem` directives can be used in `ngMenu` and `ngMenuBar` to represent a choice or action a user can take. They can also act as triggers for sub-menus.

```
<div ngMenu (itemSelected)="doAction()">
  <div ngMenuItem value="Action Item">Action Item</div>
  <div ngMenuItem value="Submenu Trigger" [submenu]="anotherMenu">Submenu Trigger</div>
</div>
```

Jump to details

----
url: https://angular.dev/api/common/getLocaleDayPeriods
----

```
function getLocaleDayPeriods(
  locale: string,
  formStyle: FormStyle,
  width: TranslationWidth,
): readonly [string, string];
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Use `Intl.DateTimeFormat` for date formating instead.

@paramlocale`string`

A locale code for the locale format rules to use.

@paramformStyle`FormStyle`

The required grammatical form.

@paramwidth`TranslationWidth`

The required character width.

@returns`readonly [string, string]`

An array of localized period strings. For example, `[AM, PM]` for `en-US`.

Jump to details

----
url: https://angular.dev/api/core/Pipe
----

```
@Pipe({
  name: string;
  pure?: boolean | undefined;
  standalone?: boolean | undefined;
})
```

The pipe name to use in template bindings. Typically uses lowerCamelCase because the name cannot contain hyphens.

When true, the pipe is pure, meaning that the `transform()` method is invoked only when its input arguments change. Pipes are pure by default.

If the pipe has internal state (that is, the result depends on state other than its arguments), set `pure` to false. In this case, the pipe is invoked on each change-detection cycle, even if the arguments have not changed.

Angular pipes marked as `standalone` do not need to be declared in an NgModule. Such pipes don't depend on any "intermediate context" of an NgModule (ex. configured providers).

## [Description](#description)

Decorator that marks a class as pipe and supplies configuration metadata.

A pipe class must implement the [`PipeTransform`](/api/core/PipeTransform) interface. For example, if the name is "myPipe", use a template binding expression such as the following:

```
{{ exp | myPipe }}
```

The result of the expression is passed to the pipe's `transform()` method.

A pipe must belong to an NgModule in order for it to be available to a template. To make it a member of an NgModule, list it in the `declarations` field of the [`NgModule`](/api/core/NgModule) metadata.

Jump to details

----
url: https://angular.dev/api/core/getDebugNode
----

```
function getDebugNode(nativeNode: any): DebugNode | null;
```

@paramnativeNode`any`

@returns`DebugNode | null`

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/ToObservableOptions
----

```
    
      interface ToObservableOptions {}
    
    
```

The `Injector` to use when creating the underlying `effect` which watches the signal.

If this isn't specified, the current [injection context](guide/di/dependency-injection-context) will be used.

Jump to details

----
url: https://angular.dev/api/common/provideImageKitLoader
----

```
    
      const provideImageKitLoader: (path: string) => Provider[];
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ClassSansProvider
----

```
    
      interface ClassSansProvider {}
    
    
```

Class to instantiate for the `token`.

Jump to details

----
url: https://angular.dev/api/forms/FormGroupDirective
----

```
    
      class FormGroupDirective extends AbstractFormDirective {}
    
    
```

Tracks the [`FormGroup`](/api/forms/FormGroup) bound to this directive.

Emits an event when the form submission has been triggered.

Returns the [`FormGroup`](/api/forms/FormGroup) bound to this directive.

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Binds an existing [`FormGroup`](/api/forms/FormGroup) or [`FormRecord`](/api/forms/FormRecord) to a DOM element.

This directive accepts an existing [`FormGroup`](/api/forms/FormGroup) instance. It will then use this [`FormGroup`](/api/forms/FormGroup) instance to match any child [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup)/[`FormRecord`](/api/forms/FormRecord), and [`FormArray`](/api/forms/FormArray) instances to child [`FormControlName`](/api/forms/FormControlName), [`FormGroupName`](/api/forms/FormGroupName), and [`FormArrayName`](/api/forms/FormArrayName) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

### [Register Form Group](#register-form-group)

The following example registers a [`FormGroup`](/api/forms/FormGroup) with first name and last name controls, and listens for the *ngSubmit* event when the button is clicked.

```
import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      @if (first.invalid) {
        <div>Name is too short.</div>
      }
      <input formControlName="first" placeholder="First name" />
      <input formControlName="last" placeholder="Last name" />

      <button type="submit">Submit</button>
    </form>
    <button (click)="setValue()">Set preset value</button>
  `,
  standalone: false,
})
export class SimpleFormGroup {
  form = new FormGroup({
    first: new FormControl('Nancy', Validators.minLength(2)),
    last: new FormControl('Drew'),
  });

  get first(): any {
    return this.form.get('first');
  }

  onSubmit(): void {
    console.log(this.form.value); // {first: 'Nancy', last: 'Drew'}
  }

  setValue() {
    this.form.setValue({first: 'Carson', last: 'Drew'});
  }
}
```

Jump to details

----
url: https://angular.dev/api/forms/UntypedFormBuilder
----

## [API](/api/forms/UntypedFormBuilder#api)

```
    
      class UntypedFormBuilder extends FormBuilder {
}
    
    
```

Like [`FormBuilder#group`](/api/forms/FormBuilder#group), except the resulting group is untyped.

@paramcontrolsConfig`{ [key: string]: any; }`

@paramoptions`AbstractControlOptions | null | undefined`

@returns`UntypedFormGroup`

@deprecated

This API is not typesafe and can result in issues with Closure Compiler renaming. Use the [`FormBuilder#group`](/api/forms/FormBuilder#group) overload with [`AbstractControlOptions`](/api/forms/AbstractControlOptions) instead.

@paramcontrolsConfig`{ [key: string]: any; }`

@paramoptions`{ [key: string]: any; }`

@returns`UntypedFormGroup`

Like [`FormBuilder#control`](/api/forms/FormBuilder#control), except the resulting control is untyped.

@paramformState`any`

@paramvalidatorOrOpts`ValidatorFn | FormControlOptions | ValidatorFn[] | null | undefined`

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

@returns`UntypedFormControl`

Like [`FormBuilder#array`](/api/forms/FormBuilder#array), except the resulting array is untyped.

@paramcontrolsConfig`any[]`

@paramvalidatorOrOpts`ValidatorFn | AbstractControlOptions | ValidatorFn[] | null | undefined`

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

@returns`UntypedFormArray`

Returns a FormBuilder in which automatically constructed [`FormControl`](/api/forms/FormControl) elements have `{nonNullable: true}` and are non-nullable.

**Constructing non-nullable controls**

When constructing a control, it will be non-nullable, and will reset to its initial value.

```
let nnfb = new FormBuilder().nonNullable;
let name = nnfb.control('Alex'); // FormControl<string>
name.reset();
console.log(name); // 'Alex'
```

**Constructing non-nullable groups or arrays**

When constructing a group or array, all automatically created inner controls will be non-nullable, and will reset to their initial values.

```
let nnfb = new FormBuilder().nonNullable;
let name = nnfb.group({who: 'Alex'}); // FormGroup<{who: FormControl<string>}>
name.reset();
console.log(name); // {who: 'Alex'}
```

**Constructing *nullable* fields on groups or arrays**

It is still possible to have a nullable field. In particular, any [`FormControl`](/api/forms/FormControl) which is *already* constructed will not be altered. For example:

```
let nnfb = new FormBuilder().nonNullable;
// FormGroup<{who: FormControl<string|null>}>
let name = nnfb.group({who: new FormControl('Alex')});
name.reset(); console.log(name); // {who: null}
```

Because the inner control is constructed explicitly by the caller, the builder has no control over how it is created, and cannot exclude the `null`.

Jump to details

----
url: https://angular.dev/api/forms/signals/createMetadataKey
----

```
function createMetadataKey<TWrite>(): MetadataKey<
  Signal<TWrite | undefined>,
  TWrite,
  TWrite | undefined
>;
function createMetadataKey<TWrite, TAcc>(
  reducer: MetadataReducer<TAcc, TWrite>,
): MetadataKey<Signal<TAcc>, TWrite, TAcc>;
```

Creates a metadata key used to contain a computed value. The last value set on a given field tree node overrides any previously set values.

@returns`MetadataKey<Signal<TWrite | undefined>, TWrite, TWrite | undefined>`

Creates a metadata key used to contain a computed value.

@paramreducer`MetadataReducer<TAcc, TWrite>`

The reducer used to combine individually set values into the final computed value.

@returns`MetadataKey<Signal<TAcc>, TWrite, TAcc>`

Jump to details

----
url: https://angular.dev/api/animations/sequence
----

```
function sequence(
  steps: AnimationMetadata[],
  options?: AnimationOptions | null,
): AnimationSequenceMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramsteps`AnimationMetadata[]`

An array of animation step objects.

* Steps defined by [`style()`](/api/animations/style) calls apply the styling data immediately.
* Steps defined by [`animate()`](/api/animations/animate) calls apply the styling data over time as specified by the timing data.

```
sequence([
style({ opacity: 0 }),
animate("1s", style({ opacity: 1 }))
])
```

@paramoptions`AnimationOptions | null`

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation.

@returns`AnimationSequenceMetadata`

## [Usage Notes](#usage-notes)

When you pass an array of steps to a [`transition()`](/api/animations/transition) call, the steps run sequentially by default. Compare this to the [`group()`](/api/animations/group) call, which runs animation steps in parallel.

When a sequence is used within a [`group()`](/api/animations/group) or a [`transition()`](/api/animations/transition) call, execution continues to the next instruction only after each of the inner animation steps have completed.

Jump to details

----
url: https://angular.dev/api/core/platformCore
----

## [API](/api/core/platformCore#api)

```
    
      const platformCore: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/CompatFieldState
----

```
    
      type CompatFieldState<TControl extends AbstractControl, TKey extends string | number = string | number, TMode extends 'writable' | 'readonly' = 'writable'> = FieldStateByMode<  TControl extends AbstractControl<unknown, infer TValue> ? TValue : never,  TKey,  TMode> & {  control: Signal<TControl>;}
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/PatternValidationError
----

```
    
      class PatternValidationError extends BaseNgValidationError {}
    
    
```

@parampattern`RegExp`

@paramoptions`ValidationErrorOptions | undefined`

@returns`PatternValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/forms/signals/ReadonlyFieldTree
----

```
    
      type ReadonlyFieldTree<TModel, TKey extends string | number = string | number> = FieldTree<  TModel,  TKey,  'readonly'>
    
    
```

Jump to details

----
url: https://angular.dev/tutorials/learn-angular
----

[](/tutorials/learn-angular/1-components-in-angular)

[](/tutorials/learn-angular/1-components-in-angular)

Welcome! Let's learn the building blocks of Angular.

This interactive tutorial will teach you the basic building blocks to start building great apps with Angular.

## [How to use this tutorial](#how-to-use-this-tutorial)

You'll need to have basic familiarity with HTML, CSS and JavaScript to understand Angular.

Each step represents a concept in Angular. You can do one, or all of them.

If you get stuck, click "Reveal answer" at the top.

Alright, let's [get started](/tutorials/learn-angular/1-components-in-angular).

## [Using AI for Development](#using-ai-for-development)

In case you're following this tutorial in your preferred AI powered IDE, [check out Angular prompt rules and best practices](/ai/develop-with-ai).

----
url: https://angular.dev/api/core/viewChild
----

```
    
      
    
    
```

Initializes a view child query. Consider using [`viewChild.required`](/api/core/viewChild) for queries that should always match.

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ read: ProviderToken<ReadT>; debugName?: string | undefined; }`

@returns`Signal<ReadT | undefined>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ debugName?: string | undefined; } | undefined`

@returns`Signal<LocatorT | undefined>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ debugName?: string | undefined; } | undefined`

@returns`Signal<LocatorT>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ read: ProviderToken<ReadT>; debugName?: string | undefined; }`

@returns`Signal<ReadT>`

## [Usage Notes](#usage-notes)

Create a child query in your component by declaring a class field and initializing it with the [`viewChild()`](/api/core/viewChild) function.

```
@Component({template: '<div #el></div><my-component #cmp />'})
export class Card {
  divEl = viewChild<ElementRef>('el');                   // Signal<ElementRef|undefined>
  divElRequired = viewChild.required<ElementRef>('el');  // Signal<ElementRef>
  cmp = viewChild(MyComponent);                          // Signal<MyComponent|undefined>
  cmpRequired = viewChild.required(MyComponent);         // Signal<MyComponent>
}
```

Jump to details

----
url: https://angular.dev/essentials/components
----

# Components

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/components.md "Edit this page")

The fundamental building block for creating applications in Angular.

Components are the main building blocks of Angular applications. Each component represents a part of a larger web page. Organizing an application into components helps provide structure to your project, clearly separating code into specific parts that are easy to maintain and grow over time.

## [Defining a component](#defining-a-component)

Every component has a few main parts:

1. A [`@Component`](/api/core/Component) [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) that contains some configuration used by Angular.
2. An HTML template that controls what renders into the DOM.
3. A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML.
4. A TypeScript class with behaviors, such as handling user input or making requests to a server.

Here is a simplified example of a `UserProfile` component.

### user-profile.ts

```
@Component({
  selector: 'user-profile',
  template: `
    <h1>User profile</h1>
    <p>This is the user profile page</p>
  `,
})
export class UserProfile {
  /* Your component code goes here */
}
```

The [`@Component`](/api/core/Component) decorator also optionally accepts a `styles` property for any CSS you want to apply to your template:

### user-profile.ts

```
@Component({
  selector: 'user-profile',
  template: `
    <h1>User profile</h1>
    <p>This is the user profile page</p>
  `,
  styles: `
    h1 {
      font-size: 3em;
    }
  `,
})
export class UserProfile {
  /* Your component code goes here */
}
```

### [Separating HTML and CSS into separate files](#separating-html-and-css-into-separate-files)

You can define a component's HTML and CSS in separate files using `templateUrl` and `styleUrl`:

### user-profile.ts

```
@Component({
  selector: 'user-profile',
  templateUrl: 'user-profile.html',
  styleUrl: 'user-profile.css',
})
export class UserProfile {
  // Component behavior is defined in here
}
```

### user-profile.html

```
<h1>User profile</h1>
<p>This is the user profile page</p>
```

### user-profile.css

```
h1 {
  font-size: 3em;
}
```

## [Using components](#using-components)

You build an application by composing multiple components together. For example, if you are building a user profile page, you might break the page up into several components like this:

Here, the `UserProfile` component uses several other components to produce the final page.

To import and use a component, you need to:

1. In your component's TypeScript file, add an `import` statement for the component you want to use.
2. In your [`@Component`](/api/core/Component) decorator, add an entry to the `imports` array for the component you want to use.
3. In your component's template, add an element that matches the selector of the component you want to use.

Here's an example of a `UserProfile` component importing a `ProfilePhoto` component:

### user-profile.ts

```
import {ProfilePhoto} from './profile-photo';

@Component({
  selector: 'user-profile',
  imports: [ProfilePhoto],
  template: `
    <h1>User profile</h1>
    <profile-photo />
    <p>This is the user profile page</p>
  `,
})
export class UserProfile {
  // Component behavior is defined in here
}
```

**TIP:** Want to know more about Angular components? See the [In-depth Components guide](guide/components) for the full details.

## [Next Step](#next-step)

Now that you know how components work in Angular, it's time to learn how we add and manage dynamic data in our application.

----
url: https://angular.dev/tutorials/signals/1-creating-your-first-signal
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/2-deriving-state-with-computed-signals)

[](/tutorials/signals/2-deriving-state-with-computed-signals)

Welcome to the Angular signals tutorial! [Signals](/essentials/signals) are Angular's reactive primitive that provide a way to manage state and automatically update your UI when that state changes.

In this activity, you'll learn how to:

* Create your first signal using the [`signal()`](/api/core/signal) function
* Display its value in a template
* Update the signal value using `set()` and `update()` methods

Let's build an interactive user status system with signals!

***

1. ### [Import the signal function](#import-the-signal-function)

   Import the [`signal`](/api/core/signal) function from `@angular/core` at the top of your component file.

   ```
   import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
   ```

2. ### [Create a signal in your component](#create-a-signal-in-your-component)

   Add a `userStatus` signal to your component class that is initialized with a value of `'offline'`.

   ```
   @Component({
     /* Config omitted */
   })
   export class App {
     userStatus = signal<'online' | 'offline'>('offline');
   }
   ```

3. ### [Display the signal value in the template](#display-the-signal-value-in-the-template)

   Update the status indicator to display the current user status by:

   1. Binding the signal to the class attribute with `[class]="userStatus()"`
   2. Displaying the status text by replacing `???` with `{{ userStatus() }}`

   ```
   <!-- Update from: -->
   <div class="status-indicator offline">
     <span class="status-dot"></span>
     Status: ???
   </div>

   <!-- To: -->
   <div class="status-indicator" [class]="userStatus()">
     <span class="status-dot"></span>
     Status: {{ userStatus() }}
   </div>
   ```

   Notice how we call the signal `userStatus()` with parentheses to read its value.

4. ### [Add methods to update the signal](#add-methods-to-update-the-signal)

   Add methods to your component that change the user status using the `set()` method.

   ```
   goOnline() {
     this.userStatus.set('online');
   }

   goOffline() {
     this.userStatus.set('offline');
   }
   ```

   The `set()` method replaces the signal's value entirely with a new value.

5. ### [Wire up the control buttons](#wire-up-the-control-buttons)

   The buttons are already in the template. Now connect them to your methods by adding:

   1. Click handlers with `(click)`
   2. Disabled states with `[disabled]` when already in that status

   ```
   <!-- Add bindings to the existing buttons: -->
   <button (click)="goOnline()" [disabled]="userStatus() === 'online'">Go Online</button>
   <button (click)="goOffline()" [disabled]="userStatus() === 'offline'">Go Offline</button>
   ```

6. ### [Add a toggle method using update()](#add-a-toggle-method-using-update)

   Add a `toggleStatus()` method that switches between online and offline using the `update()` method.

   ```
   toggleStatus() {
     this.userStatus.update(current => current === 'online' ? 'offline' : 'online');
   }
   ```

   The `update()` method takes a function that receives the current value and returns the new value. This is useful when you need to modify the existing value based on its current state.

7. ### [Add the toggle button handler](#add-the-toggle-button-handler)

   The toggle button is already in the template. Connect it to your `toggleStatus()` method:

   ```
   <button (click)="toggleStatus()" class="toggle-btn">Toggle Status</button>
   ```

Congratulations! You've created your first signal and learned how to update it using both `set()` and `update()` methods. The [`signal()`](/api/core/signal) function creates a reactive value that Angular tracks, and when you update it, your UI automatically reflects the changes.

Next, you'll learn [how to derive state from signals using computed](/tutorials/signals/2-deriving-state-with-computed-signals)!

----
url: https://angular.dev/api/core/ElementRef
----

```
    
      class ElementRef<T = any> {}
    
    
```

@paramnativeElement`T`

@returns`ElementRef<T>`

Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. If used, it is recommended in combination with [`DomSanitizer`](/best-practices/security#direct-use-of-the-dom-apis-and-explicit-sanitization-calls) for maxiumum security;

## [Description](#description)

A wrapper around a native element inside of a View.

An [`ElementRef`](/api/core/ElementRef) is backed by a render-specific element. In the browser, this is usually a DOM element.

Jump to details

----
url: https://angular.dev/api/forms/AbstractControl
----

## [API](/api/forms/AbstractControl#api)

```
    
      abstract class AbstractControl<TValue = any, TRawValue extends TValue = TValue, TValueWithOptionalControlStates = any> {
}
    
    
```

Initialize the AbstractControl instance.

@paramvalidators`ValidatorFn | ValidatorFn[] | null`

The function or array of functions that is used to determine the validity of this control synchronously.

@paramasyncValidators`AsyncValidatorFn | AsyncValidatorFn[] | null`

The function or array of functions that is used to determine validity of this control asynchronously.

@returns`AbstractControl<TValue, TRawValue, TValueWithOptionalControlStates>`

```
// Reference to the RequiredValidator
const ctrl = new FormControl<string | null>('', Validators.required);
ctrl.removeValidators(Validators.required);

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<string | null>('', minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)

ctrl.removeValidators(minValidator);
```

```
// Reference to the RequiredValidator
const ctrl = new FormControl<number | null>(0, Validators.required);
expect(ctrl.hasValidator(Validators.required)).toEqual(true)

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<number | null>(0, minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)
```

Sets the value of the control. Abstract method (implemented in sub-classes).

@paramvalue`TRawValue`

@paramoptions`Object | undefined`

@returns`void`

Patches the value of the control. Abstract method (implemented in sub-classes).

@paramvalue`TValue`

@paramoptions`Object | undefined`

@returns`void`

Resets the control. Abstract method (implemented in sub-classes).

@paramvalue`TValueWithOptionalControlStates | undefined`

@paramoptions`Object | undefined`

@returns`void`

The raw value of this control. For most control implementations, the raw value will include disabled children.

@returns`any`


### [Manually set the errors for a control](/api/forms/AbstractControl#manually-set-the-errors-for-a-control)

```
const login = new FormControl('someLogin');
login.setErrors({
  notUnique: true
});

expect(login.valid).toEqual(false);
expect(login.errors).toEqual({ notUnique: true });

login.setValue('someOtherLogin');

expect(login.valid).toEqual(true);
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

If no path is given, this method checks for the error on the current control.

Retrieves the top-level ancestor of this control.

## [Description](/api/forms/AbstractControl#description)

This is the base class for [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup), and [`FormArray`](/api/forms/FormArray).

It provides some of the shared behavior that all controls and groups of controls have, like running validators, calculating status, and resetting state. It also defines the properties that are shared between all sub-classes, like `value`, `valid`, and `dirty`. It shouldn't be instantiated directly.

The first type parameter TValue represents the value type of the control (`control.value`). The optional type parameter TRawValue represents the raw value type (`control.getRawValue()`).

Jump to details

----
url: https://angular.dev/api/cdk/testing/BaseHarnessFilters
----

```
    
      interface BaseHarnessFilters {}
    
    
```

Only find instances whose host element matches the given selector.

Only find instances that are nested under an element with the given selector.

Jump to details

----
url: https://angular.dev/api/elements/WithProperties
----

```
    
      type WithProperties<P> = {  [property in keyof P]: P[property];}
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/AsyncOptionPredicate
----

```
    
      type AsyncOptionPredicate<T, O> = (item: T, option: O) => Promise<boolean>
    
    
```

Jump to details

----
url: https://angular.dev/api/aria/accordion/AccordionContent
----

```
    
      class AccordionContent {}
    
    
```

## [Description](#description)

A structural directive that provides a mechanism for lazily rendering the content for an `ngAccordionPanel`.

This directive should be applied to an `ng-template` inside an `ngAccordionPanel`. It allows the content of the panel to be lazily rendered, improving performance by only creating the content when the panel is first expanded.

```
<div ngAccordionPanel>
  <ng-template ngAccordionContent>
    <p>This is the content that will be displayed inside the panel.</p>
  </ng-template>
</div>
```

Jump to details

----
url: https://angular.dev/api/common/http/httpResource
----

```
    
      const httpResource: HttpResourceFn;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/globals/getComponent
----

```
function getComponent<T>(element: Element): T | null;
```

@paramelement`Element`

DOM element from which the component should be retrieved.

@returns`T | null`

Component instance associated with the element or `null` if there is no component associated with it.

## [Usage Notes](#usage-notes)

Given the following DOM structure:

```
<app-root>
  <div>
    <child-comp></child-comp>
  </div>
</app-root>
```

Calling [`getComponent`](/api/core/globals/getComponent) on `<child-comp>` will return the instance of `ChildComponent` associated with this DOM element.

Calling the function on `<app-root>` will return the `MyApp` instance.

Jump to details

----
url: https://angular.dev/api/platform-browser/platformBrowser
----

```
    
      const platformBrowser: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
    
    
```

Jump to details

----
url: https://angular.dev/api/aria/listbox/Option
----

```
    
      class Option<V> implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

Whether the option is currently active (focused).

A unique identifier for the option.

The value of the option.

Whether an item is disabled.

The text used by the typeahead search.

Whether the option is selected.

@returns`void`

@returns`void`

## [Description](#description)

A selectable option in an `ngListbox`.

This directive should be applied to an element (e.g., `<li>`, `<div>`) within an `ngListbox`. The `value` input is used to identify the option, and the `label` input provides the accessible name for the option.

```
<li ngOption value="item-id" label="Item Name">
  Item Name
</li>
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/protractor/ProtractorHarnessEnvironment
----

* * [ProtractorElement](/api/cdk/testing/protractor/ProtractorElement)
  * [ProtractorHarnessEnvironment](/api/cdk/testing/protractor/ProtractorHarnessEnvironment)
  * [ProtractorHarnessEnvironmentOptions](/api/cdk/testing/protractor/ProtractorHarnessEnvironmentOptions)

Deprecation warning

As of v13.0.0, this environment no longer works, as it is not compatible with the new [Angular Package Format](https://angular.dev/tools/libraries/angular-package-format).

## [API](#api)

```
    
      class ProtractorHarnessEnvironment extends HarnessEnvironment<ElementFinder> {}
    
    
```

@paramrawRootElement`ElementFinder`

@paramoptions`ProtractorHarnessEnvironmentOptions | undefined`

@returns`ProtractorHarnessEnvironment`

Flushes change detection and async tasks captured in the Angular zone. In most cases it should not be necessary to call this manually. However, there may be some edge cases where it is needed to fully flush animation events.

@returns`Promise<void>`

Gets the root element for the document.

@returns`ElementFinder`

Creates a `TestElement` from a raw element.

@paramelement`ElementFinder`

@returns`TestElement`

Creates a `HarnessLoader` rooted at the given raw element.

@paramelement`ElementFinder`

@returns`HarnessEnvironment<ElementFinder>`

Gets a list of all elements matching the given selector under this environment's root element.

@paramselector`string`

@returns`Promise<ElementFinder[]>`

Creates a `HarnessLoader` rooted at the document root.

@paramoptions`ProtractorHarnessEnvironmentOptions | undefined`

@returns`HarnessLoader`

Gets the ElementFinder corresponding to the given TestElement.

@paramel`TestElement`

@returns`ElementFinder`

Jump to details

----
url: https://angular.dev/api/common/http/HttpXsrfTokenExtractor
----

```
    
      abstract class HttpXsrfTokenExtractor {}
    
    
```

Get the XSRF token to use with an outgoing request.

Will be called for every request, so the token may change between requests.

@returns`string | null`

Jump to details

----
url: https://angular.dev/api/core/model
----

```
    
      
    
    
```

Initializes a model of type `T` with an initial value of `undefined`. Angular will implicitly use `undefined` as initial value.

@returns`ModelSignal<T | undefined>`

Initializes a model of type `T` with the given initial value.

@paraminitialValue`T`

@paramopts`ModelOptions | undefined`

@returns`ModelSignal<T>`

Initializes a required model.

Users of your directive/component need to bind to the input side of the model. If unset, a compile time error will be reported.

@paramopts`ModelOptions | undefined`

@returns`ModelSignal<T>`

## [Usage Notes](#usage-notes)

To use [`model()`](/api/core/model), import the function from `@angular/core`.

```
import {model} from '@angular/core';
```

Inside your component, introduce a new class member and initialize it with a call to [`model`](/api/core/model) or [`model.required`](/api/core/model).

```
@Directive({
  ...
})
export class MyDir {
  firstName = model<string>();            // ModelSignal<string|undefined>
  lastName  = model.required<string>();   // ModelSignal<string>
  age       = model(0);                   // ModelSignal<number>
}
```

Inside your component template, you can display the value of a [`model`](/api/core/model) by calling the signal.

```
<span>{{firstName()}}</span>
```

Updating the [`model`](/api/core/model) is equivalent to updating a writable signal.

```
updateName(newFirstName: string): void {
  this.firstName.set(newFirstName);
}
```

Jump to details

----
url: https://angular.dev/api/forms/FormsModule
----

```
    
      class FormsModule {}
    
    
```

Provides options for configuring the forms module.

@paramopts`{ callSetDisabledState?: SetDisabledStateOption | undefined; }`

An object of configuration options

* `callSetDisabledState` Configures whether to `always` call `setDisabledState`, which is more correct, or to only call it `whenDisabled`, which is the legacy behavior.

@returns`ModuleWithProviders<FormsModule>`

Jump to details

----
url: https://angular.dev/errors/NG05101
----

This error occurs when Angular tries to set up a listener for an event but none of the registered event manager plugins recognizes the event name.

## [Debugging the error](#debugging-the-error)

The error message includes the unrecognized event name. It occurs only when no registered plugin supports the event, for example when an [`EventManager`](/api/platform-browser/EventManager) is created with a custom plugin list that does not include `DomEventsPlugin`, as in unit tests or custom setups.

If the event name is intentional and belongs to a third-party plugin (for example, a Hammer.js gesture event), make sure the plugin is provided via the [`EVENT_MANAGER_PLUGINS`](/api/platform-browser/EVENT_MANAGER_PLUGINS) token:

```
import {ApplicationConfig} from '@angular/core';
import {EVENT_MANAGER_PLUGINS} from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [{provide: EVENT_MANAGER_PLUGINS, useClass: MyCustomPlugin, multi: true}],
};
```

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragDrop
----

```
    
      interface CdkDragDrop<T, O = T, I = any> {}
    
    
```

Index of the item when it was picked up.

Current index of the item.

Item that is being dropped.

Container in which the item was dropped.

Container from which the item was picked up. Can be the same as the `container`.

Whether the user's pointer was over the container when the item was dropped.

Distance in pixels that the user has dragged since the drag sequence started.

Position where the pointer was when the item was dropped

Native event that caused the drop event.

Jump to details

----
url: https://angular.dev/api/core/assertNotInReactiveContext
----

```
function assertNotInReactiveContext(
  debugFn: Function,
  extraContext?: string | undefined,
): void;
```

@paramdebugFn`Function`

a reference to the function making the assertion (used for the error message).

@paramextraContext`string | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/NavigationCancel
----

```
    
      class NavigationCancel extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramreason`string`

A description of why the navigation was cancelled. For debug purposes only. Use `code` instead for a stable cancellation reason that can be used in production.

@paramcode`NavigationCancellationCode | undefined`

A code to indicate why the navigation was canceled. This cancellation code is stable for the reason and can be relied on whereas the `reason` string could change and should not be used in production.

@returns`NavigationCancel`

A description of why the navigation was cancelled. For debug purposes only. Use `code` instead for a stable cancellation reason that can be used in production.

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/cli/serve
----

```
ng serve [project] [options]
```

```
ng dev [project] [options]
```

```
ng s [project] [options]
```

Builds and serves your application, rebuilding on file changes.

## [Arguments](/cli/serve#arguments)

`project`

The name of the project to build. Can be an application or a library.

Value Type`string`

## [Options](/cli/serve#options)

`allowed-hosts`

The hosts that the development server will respond to. This option sets the Vite option of the same name. For further details: <https://vite.dev/config/server-options.html#server-allowedhosts>

Value Type`boolean`

`build-target`

A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.

Value Type`string`

`configuration`

Alias`c`

One or more named builder configurations as a comma-separated list as specified in the "configurations" section in angular.json. The builder uses the named configurations to run the given target. For more information, see [https://angular.dev/reference/configs/workspace-config#alternate-build-configurations](/reference/configs/workspace-config#alternate-build-configurations).

Value Type`string`

`define`

Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.

Value Type`array`

`headers`

Custom HTTP headers to be added to all responses.

Value Type`array`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`hmr`

Enable hot module replacement. Defaults to the value of 'liveReload'. Currently, only global and component stylesheets are supported.

Value Type`boolean`

`host`

Host to listen on.

Value Type`string`Default`localhost`

`inspect`

Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are enabled.

Value Type`string`

`live-reload`

Whether to reload the page on change, using live-reload.

Value Type`boolean`Default`true`

`open`

Alias`o`

Opens the url in default browser.

Value Type`boolean`Default`false`

`poll`

Enable and define the file watching poll time period in milliseconds.

Value Type`number`

`port`

Port to listen on.

Value Type`number`Default`4200`

`prebundle`

Enable and control the Vite-based development server's prebundling capabilities. To enable prebundling, the Angular CLI cache must also be enabled.

Value Type`boolean`Default`true`

`proxy-config`

Proxy configuration file. For more information, see [https://angular.dev/tools/cli/serve#proxying-to-a-backend-server](/tools/cli/serve#proxying-to-a-backend-server).

Value Type`string`

`serve-path`

The pathname where the application will be served.

Value Type`string`

`ssl`

Serve using HTTPS.

Value Type`boolean`Default`false`

`ssl-cert`

SSL certificate to use for serving HTTPS.

Value Type`string`

`ssl-key`

SSL key to use for serving HTTPS.

Value Type`string`

`verbose`

Adds more details to output logging.

Value Type`boolean`

`watch`

Rebuild on change.

Value Type`boolean`Default`true`

Jump to details

----
url: https://angular.dev/errors/NG02825
----

Angular produces this error when [`HttpClient`](/api/common/http/HttpClient) uses the fetch backend during server-side rendering and the bytes buffered for a response body exceed the configured `maxResponseBodySize` limit. By default, Angular limits each buffered response body to 1 MB to prevent the server from buffering unexpectedly large responses during rendering.

## [Fixing the error](/errors/NG02825#fixing-the-error)

Set `maxResponseBodySize` in the `provideServerRendering` options to increase the limit:

```
import {provideServerRendering, withRoutes} from '@angular/ssr';
import {serverRoutes} from './app.routes.server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(
      {
        maxResponseBodySize: 5 * 1024 * 1024, // 5MB
      },
      withRoutes(serverRoutes),
    ),
  ],
};
```

`maxResponseBodySize` configures the maximum number of response body bytes Angular buffers for each server-side [`HttpClient`](/api/common/http/HttpClient) request that uses the fetch backend. The value is configured in bytes and applies globally.

**IMPORTANT:** Keep this limit as small as your application allows. Increasing it lets server-side requests buffer larger response bodies, which can increase memory use and denial-of-service risk. Prefer moving large downloads outside server rendering.

## [Debugging the error](/errors/NG02825#debugging-the-error)

The error message includes the configured `maxResponseBodySize` limit in bytes. To identify which HTTP request is returning a large response, check your network logs or the SSR server console for the failing request URL.

If the large response is only needed on the client and not required for server-side rendering, you can disable the transfer cache for that specific request:

```
httpClient.get('/api/large-data', {transferCache: false});
```

----
url: https://angular.dev/tools/cli/build
----

You can build your Angular CLI application or library with the `ng build` command. This will compile your TypeScript code to JavaScript, as well as optimize, bundle, and minify the output as appropriate.

`ng build` only executes the builder for the `build` target in the default project as specified in `angular.json`. Angular CLI includes four builders typically used as `build` targets:

| Builder                                         | Purpose                                                                                                                                                                                                                                        |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@angular/build:application`                    | Builds an application with a client-side bundle, a Node server, and build-time prerendered routes with [esbuild](https://esbuild.github.io/).                                                                                                  |
| `@angular-devkit/build-angular:browser-esbuild` | Bundles a client-side application for use in a browser with [esbuild](https://esbuild.github.io/). See [`browser-esbuild` documentation](tools/cli/build-system-migration#manual-migration-to-the-compatibility-builder) for more information. |
| `@angular-devkit/build-angular:browser`         | Bundles a client-side application for use in a browser with [webpack](https://webpack.js.org/).                                                                                                                                                |
| `@angular/build:ng-packagr`                     | Builds an Angular library adhering to [Angular Package Format](tools/libraries/angular-package-format).                                                                                                                                        |

Applications generated by `ng new` use `@angular/build:application` by default. Libraries generated by `ng generate library` use `@angular/build:ng-packagr` by default.

You can determine which builder is being used for a particular project by looking up the `build` target for that project.

```
{
  "projects": {
    "my-app": {
      "architect": {
        // `ng build` invokes the Architect target named `build`.
        "build": {
          "builder": "@angular/build:application",
          …
        },
        "serve": { … }
        "test": { … }
        …
      }
    }
  }
}
```

This page discusses usage and options of `@angular/build:application`.

## [Output directory](#output-directory)

The result of this build process is output to a directory (`dist/${PROJECT_NAME}` by default).

## [Configuring size budgets](#configuring-size-budgets)

As applications grow in functionality, they also grow in size. The CLI lets you set size thresholds in your configuration to ensure that parts of your application stay within size boundaries that you define.

Define your size boundaries in the CLI configuration file, `angular.json`, in a `budgets` section for each [configured environment](tools/cli/environments).

```
{
  …
  "configurations": {
    "production": {
      …
      "budgets": [
        {
          "type": "initial",
          "maximumWarning": "250kb",
          "maximumError": "500kb"
        },
      ]
    }
  }
}
```

You can specify size budgets for the entire app, and for particular parts. Each budget entry configures a budget of a given type. For example, the `initial` budget measures the JavaScript and CSS used to bootstrap the application, which corresponds to the `Initial Total` value shown in the build output summary. Specify size values in the following formats:

| Size value      | Details                                                                   |
| :-------------- | :------------------------------------------------------------------------ |
| `123` or `123b` | Size in bytes.                                                            |
| `123kb`         | Size in kilobytes.                                                        |
| `123mb`         | Size in megabytes.                                                        |
| `12%`           | Percentage of size relative to baseline. (Not valid for baseline values.) |

When you configure a budget, the builder warns or reports an error when a given part of the application reaches or exceeds a boundary size that you set.

Each budget entry is a JSON object with the following properties:

| Property       | Value                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| :------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| type           | The type of budget. One of:Value	Detailsbundle	The size of a specific bundle. Use this type together with name to budget a specific bundle, including a lazy-loaded bundle.&#xA;initial	The size of JavaScript and CSS needed for bootstrapping the application. This corresponds to the Initial Total value shown in the build output summary. Defaults to warning at 500kb and erroring at 1mb.&#xA;allScript	The size of all scripts.&#xA;all	The size of the entire application.&#xA;anyComponentStyle	This size of any one component stylesheet. Defaults to warning at 2kb and erroring at 4kb.&#xA;anyScript	The size of any one script.&#xA;any	The size of any file. |
| name           | The bundle name for `type=bundle`. This is the bundle name reported by the builder, not the generated output filename.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| baseline       | The baseline size for comparison. If not set, the baseline defaults to `0`, and threshold values are calculated relative to that baseline.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| maximumWarning | The maximum threshold for warning relative to the baseline.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| maximumError   | The maximum threshold for error relative to the baseline.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| minimumWarning | The minimum threshold for warning relative to the baseline.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| minimumError   | The minimum threshold for error relative to the baseline.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| warning        | The threshold for warning relative to the baseline (min & max).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| error          | The threshold for error relative to the baseline (min & max).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

To configure a budget for a lazy-loaded bundle, use `type: "bundle"` and set `name` to that bundle's name.

```
{
  "budgets": [
    {
      "type": "bundle",
      "name": "admin",
      "maximumWarning": "250kb",
      "maximumError": "300kb"
    }
  ]
}
```

The `name` field matches the bundle name, not the emitted filename, so it does not use wildcard or regular expression patterns such as `admin.*.js`.

The following example shows a budget that uses a baseline:

```
{
  "type": "bundle",
  "name": "main",
  "baseline": "200kb",
  "maximumWarning": "10%",
  "maximumError": "20%"
}
```

In this example, the builder warns when the bundle grows beyond `220kb` and errors when it grows beyond `240kb`.

## [Configuring CommonJS dependencies](#configuring-commonjs-dependencies)

Always prefer native [ECMAScript modules](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/import) (ESM) throughout your application and its dependencies. ESM is a fully specified web standard and JavaScript language feature with strong static analysis support. This makes bundle optimizations more powerful than other module formats.

Angular CLI also supports importing [CommonJS](https://nodejs.org/api/modules.html) dependencies into your project and will bundle these dependencies automatically. However, CommonJS modules can prevent bundlers and minifiers from optimizing those modules effectively, which results in larger bundle sizes. For more information, see [How CommonJS is making your bundles larger](https://web.dev/commonjs-larger-bundles).

Angular CLI outputs warnings if it detects that your browser application depends on CommonJS modules. When you encounter a CommonJS dependency, consider asking the maintainer to support ECMAScript modules, contributing that support yourself, or using an alternative dependency which meets your needs. If the best option is to use a CommonJS dependency, you can disable these warnings by adding the CommonJS module name to `allowedCommonJsDependencies` option in the `build` options located in `angular.json`.

```
"build": {
  "builder": "@angular/build:application",
  "options": {
     "allowedCommonJsDependencies": [
        "lodash"
     ]
     …
   }
   …
},
```

## [Configuring browser compatibility](#configuring-browser-compatibility)

The Angular CLI uses [Browserslist](https://github.com/browserslist/browserslist) to ensure compatibility with different browser versions. Depending on supported browsers, Angular will automatically transform certain JavaScript and CSS features to ensure the built application does not use a feature which has not been implemented by a supported browser. However, the Angular CLI will not automatically add polyfills to supplement missing Web APIs. Use the `polyfills` option in `angular.json` to add polyfills.

By default, the Angular CLI uses a `browserslist` configuration which [matches browsers supported by Angular](reference/versions#browser-support) for the current major version.

To override the internal configuration, run [`ng generate config browserslist`](cli/generate/config), which generates a `.browserslistrc` configuration file in the project directory matching Angular's supported browsers.

See the [browserslist repository](https://github.com/browserslist/browserslist) for more examples of how to target specific browsers and versions. Avoid expanding this list to more browsers. Even if your application code more broadly compatible, Angular itself might not be. You should only ever *reduce* the set of browsers or versions in this list.

**HELPFUL:** Use [browsersl.ist](https://browsersl.ist) to display compatible browsers for a `browserslist` query.

## [Configuring Tailwind](#configuring-tailwind)

Angular supports [Tailwind CSS](https://tailwindcss.com/), a utility-first CSS framework.

To integrate Tailwind CSS with Angular CLI, see [Using Tailwind CSS with Angular](guide/tailwind)

## [Critical CSS inlining](#critical-css-inlining)

Angular can inline the critical CSS definitions of your application to improve [First Contentful Paint (FCP)](https://web.dev/first-contentful-paint). This option is enabled default. You can disable this inlining in the [`styles` customization options](reference/configs/workspace-config#styles-optimization-options).

This optimization extracts the CSS needed to render the initial viewport and inlines it directly into the generated HTML, allowing the browser to display content faster without waiting for the full stylesheets to load. The remaining CSS then loads asynchronously in the background. Angular CLI uses [Beasties](https://github.com/danielroe/beasties) to analyze your application’s HTML and styles.

----
url: https://angular.dev/api/core/DefaultExport
----

```
    
      interface DefaultExport<T> {}
    
    
```

Default exports are bound under the name `"default"`, per the ES Module spec: <https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records>

Jump to details

----
url: https://angular.dev/api/forms/ReactiveFormsModule
----

```
    
      class ReactiveFormsModule {}
    
    
```

Provides options for configuring the reactive forms module.

@paramopts`{ warnOnNgModelWithFormControl?: "always" | "never" | "once" | undefined; callSetDisabledState?: SetDisabledStateOption | undefined; }`

An object of configuration options

* `warnOnNgModelWithFormControl` Configures when to emit a warning when an `ngModel` binding is used with reactive form directives.
* `callSetDisabledState` Configures whether to `always` call `setDisabledState`, which is more correct, or to only call it `whenDisabled`, which is the legacy behavior.

@returns`ModuleWithProviders<ReactiveFormsModule>`

Jump to details

----
url: https://angular.dev/api/common/http/withFetch
----

[`withFetch`](/api/common/http/withFetch) is not required anymore. [`FetchBackend`](/api/common/http/FetchBackend) is the default [`HttpBackend`](/api/common/http/HttpBackend).

## [API](/api/common/http/withFetch#api)

```
function withFetch(): HttpFeature<HttpFeatureKind.Fetch>;
```

@deprecated

[`withFetch`](/api/common/http/withFetch) is not required anymore. [`FetchBackend`](/api/common/http/FetchBackend) is the default [`HttpBackend`](/api/common/http/HttpBackend).

@returns`HttpFeature<HttpFeatureKind.Fetch>`

## [Description](/api/common/http/withFetch#description)

Configures the current [`HttpClient`](/api/common/http/HttpClient) instance to make requests using the fetch API.

Note: The Fetch API doesn't support progress report on uploads.

Jump to details

----
url: https://angular.dev/api/core/AbstractType
----

```
    
      interface AbstractType<T> extends Function {}
    
    
```

Calls the function, substituting the specified object for the this value of the function, and the specified array for the arguments of the function.

@paramthis`Function`

@paramthisArg`any`

The object to be used as the this object.

@paramargArray`any`

A set of arguments to be passed to the function.

@returns`any`

Calls a method of an object, substituting another object for the current object.

@paramthis`Function`

@paramthisArg`any`

The object to be used as the current object.

@paramargArray`any[]`

A list of arguments to be passed to the method.

@returns`any`

For a given function, creates a bound function that has the same body as the original function. The this object of the bound function is associated with the specified object, and has the specified initial parameters.

@paramthis`Function`

@paramthisArg`any`

An object to which the this keyword can refer inside the new function.

@paramargArray`any[]`

A list of arguments to be passed to the new function.

@returns`any`

Returns a string representation of a function.

@returns`string`

Returns the name of the function. Function names are read-only and can not be changed.

Determines whether the given value inherits from this function if this function was used as a constructor function.

A constructor function can control which objects are recognized as its instances by 'instanceof' by overriding this method.

@paramvalue`any`

@returns`boolean`

Jump to details

----
url: https://angular.dev/api/forms/signals/compat/compatForm
----

```
function compatForm<TModel>(model: WritableSignal<TModel>): FieldTree<TModel>;
function compatForm<TModel>(
  model: WritableSignal<TModel>,
  schemaOrOptions: any,
): FieldTree<TModel>;
function compatForm<TModel>(
  model: WritableSignal<TModel>,
  schema: SchemaOrSchemaFn<TModel>,
  options: CompatFormOptions<TModel>,
): FieldTree<TModel>;
```

Creates a compatibility form wrapped around the given model data.

[`compatForm`](/api/forms/signals/compat/compatForm) is a version of the `form` function that is designed for backwards compatibility with Reactive forms by accepting Reactive controls as a part of the data.

@parammodel`WritableSignal<TModel>`

A writable signal that contains the model data for the form. The resulting field structure will match the shape of the model and any changes to the form data will be written to the model.

@returns`FieldTree<TModel>`

Usage notes

```
const lastName = new FormControl('lastName');

const nameModel = signal({
   first: '',
   last: lastName
});

const nameForm = compatForm(nameModel, (name) => {
  required(name.first);
});

nameForm.last().value(); // lastName, not FormControl
```

Creates a compatibility form wrapped around the given model data.

[`compatForm`](/api/forms/signals/compat/compatForm) is a version of the `form` function that is designed for backwards compatibility with Reactive forms by accepting Reactive controls as a part of the data.

@parammodel`WritableSignal<TModel>`

A writable signal that contains the model data for the form. The resulting field structure will match the shape of the model and any changes to the form data will be written to the model.

@paramschemaOrOptions`any`

The second argument can be either

1. A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.). When passing a schema, the form options can be passed as a third argument if needed.
2. The form options (excluding adapter, since it's provided).

@returns`FieldTree<TModel>`

Usage notes

```
const lastName = new FormControl('lastName');

const nameModel = signal({
   first: '',
   last: lastName
});

const nameForm = compatForm(nameModel, (name) => {
  required(name.first);
});

nameForm.last().value(); // lastName, not FormControl
```

Creates a compatibility form wrapped around the given model data.

[`compatForm`](/api/forms/signals/compat/compatForm) is a version of the `form` function that is designed for backwards compatibility with Reactive forms by accepting Reactive controls as a part of the data.

@parammodel`WritableSignal<TModel>`

A writable signal that contains the model data for the form. The resulting field structure will match the shape of the model and any changes to the form data will be written to the model.

@paramschema`SchemaOrSchemaFn<TModel>`

@paramoptions`CompatFormOptions<TModel>`

The form options (excluding adapter, since it's provided).

@returns`FieldTree<TModel>`

Usage notes

```
const lastName = new FormControl('lastName');

const nameModel = signal({
   first: '',
   last: lastName
});

const nameForm = compatForm(nameModel, (name) => {
  required(name.first);
});

nameForm.last().value(); // lastName, not FormControl
```

## [Description](#description)

Creates a compatibility form wrapped around the given model data.

[`compatForm`](/api/forms/signals/compat/compatForm) is a version of the `form` function that is designed for backwards compatibility with Reactive forms by accepting Reactive controls as a part of the data.

## [Usage Notes](#usage-notes)

```
const lastName = new FormControl('lastName');

const nameModel = signal({
   first: '',
   last: lastName
});

const nameForm = compatForm(nameModel, (name) => {
  required(name.first);
});

nameForm.last().value(); // lastName, not FormControl
```

Jump to details

----
url: https://angular.dev/api/router/LoadChildren
----

```
    
      type LoadChildren = LoadChildrenCallback
    
    
```

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/25-next-steps
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/24-create-a-pipe)

[](/tutorials/learn-angular/24-create-a-pipe)

You've done it! You completed the Learn Angular tutorial and learned the essentials of building apps.

You can also learn more in our [guides](overview) and [reference](api), or `ng new`.

### [What is Angular?](overview)

[Angular is the web development framework for building modern apps!](overview)

[Learn more](overview)

### [Angular's Roadmap](roadmap)

[Read about Angular's open source roadmap including current, future and accomplished projects.](roadmap)

[Learn more](roadmap)

### [Playground](playground)

[Play with Angular in your browser!](playground)

[Start playing](playground)

### [Angular YouTube Course](https://youtube.com/playlist?list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF)

[Check out the official Angular YouTube channel for videos, courses and more!](https://youtube.com/playlist?list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF)

[Start learning](https://youtube.com/playlist?list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF)

----
url: https://angular.dev/api/cdk/testing/parallel
----

```
function parallel<T1, T2, T3, T4, T5>(
  values: () => [
    T1 | PromiseLike<T1>,
    T2 | PromiseLike<T2>,
    T3 | PromiseLike<T3>,
    T4 | PromiseLike<T4>,
    T5 | PromiseLike<T5>,
  ],
): Promise<[T1, T2, T3, T4, T5]>;
function parallel<T1, T2, T3, T4>(
  values: () => [
    T1 | PromiseLike<T1>,
    T2 | PromiseLike<T2>,
    T3 | PromiseLike<T3>,
    T4 | PromiseLike<T4>,
  ],
): Promise<[T1, T2, T3, T4]>;
function parallel<T1, T2, T3>(
  values: () => [
    T1 | PromiseLike<T1>,
    T2 | PromiseLike<T2>,
    T3 | PromiseLike<T3>,
  ],
): Promise<[T1, T2, T3]>;
function parallel<T1, T2>(
  values: () => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>],
): Promise<[T1, T2]>;
function parallel<T>(values: () => (T | PromiseLike<T>)[]): Promise<T[]>;
```

Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change detection over the entire operation such that change detection occurs exactly once before resolving the values and once after.

@paramvalues`() => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]`

A getter for the async values to resolve in parallel with batched change detection.

@returns`Promise<[T1, T2, T3, T4, T5]>`

Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change detection over the entire operation such that change detection occurs exactly once before resolving the values and once after.

@paramvalues`() => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]`

A getter for the async values to resolve in parallel with batched change detection.

@returns`Promise<[T1, T2, T3, T4]>`

Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change detection over the entire operation such that change detection occurs exactly once before resolving the values and once after.

@paramvalues`() => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]`

A getter for the async values to resolve in parallel with batched change detection.

@returns`Promise<[T1, T2, T3]>`

Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change detection over the entire operation such that change detection occurs exactly once before resolving the values and once after.

@paramvalues`() => [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]`

A getter for the async values to resolve in parallel with batched change detection.

@returns`Promise<[T1, T2]>`

Resolves the given list of async values in parallel (i.e. via Promise.all) while batching change detection over the entire operation such that change detection occurs exactly once before resolving the values and once after.

@paramvalues`() => (T | PromiseLike<T>)[]`

A getter for the async values to resolve in parallel with batched change detection.

@returns`Promise<T[]>`

Jump to details

----
url: https://angular.dev/api/forms/signals/maxDate
----

```
function maxDate<
  TValue extends Date | null,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  maxDateValue: Date | LogicFn<TValue, Date | undefined, TPathKind>,
  config?: BaseValidatorConfig<TValue, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@parammaxDateValue`Date | LogicFn<TValue, Date | undefined, TPathKind>`

The maximum date, or a LogicFn that returns the maximum date.

@paramconfig`BaseValidatorConfig<TValue, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`maxDateError()`](/api/forms/signals/maxDateError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

## [Description](#description)

Binds a validator to the given path that requires the value to be less than or equal to the given [`maxDate`](/api/forms/signals/maxDate). This function can only be called on date paths. In addition to binding a validator, this function adds [`MAX`](/api/forms/signals/MAX) property to the field.

When the field is bound to an `<input type="date">`, its value is a `Date` at midnight UTC, so the maximum should be built the same way, e.g. `new Date('2026-08-31')` or `new Date(Date.UTC(2026, 7, 31))`. A `Date` created from local time, such as `new Date(2026, 7, 31)` or `new Date()`, is offset by the user's time zone, which can make the limit date itself invalid.

Jump to details

----
url: https://angular.dev/guide/testing/components-scenarios
----

This guide explores common component testing use cases.

## [Component binding](#component-binding)

In the example application, the `Banner` component presents static title text in the HTML template.

After a few changes, the `Banner` component presents a dynamic title by binding to the component's `title` property like this.

### banner.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-banner',
  template: '<h1>{{ title() }}</h1>',
  styles: ['h1 { color: green; font-size: 350%}'],
})
export class Banner {
  title = signal('Test Tour of Heroes');
}
```

As minimal as this is, you decide to add a test to confirm that component actually displays the right content where you think it should.

### [Query for the `<h1>`](#query-for-the-h1)

You'll write a sequence of tests that inspect the value of the `<h1>` element that wraps the *title* property interpolation binding.

You update the `beforeEach` to find that element with a standard HTML `querySelector` and assign it to the `h1` variable.

### banner.component.spec.ts

```
let component: Banner;
let fixture: ComponentFixture<Banner>;
let h1: HTMLElement;

beforeEach(() => {
  fixture = TestBed.createComponent(Banner);
  component = fixture.componentInstance; // Banner test instance
  h1 = fixture.nativeElement.querySelector('h1');
});
```

### [`createComponent()` does not bind data](#createcomponent-does-not-bind-data)

For your first test you'd like to see that the screen displays the default `title`. Your instinct is to write a test that immediately inspects the `<h1>` like this:

```
it('should display original title', () => {
  expect(h1.textContent).toContain(component.title());
});
```

*That test fails* with the message:

```
expected '' to contain 'Test Tour of Heroes'.
```

Binding happens when Angular performs **change detection**.

In production, change detection kicks in automatically when Angular creates a component or the user enters a keystroke, for example.

The [`TestBed.createComponent`](/api/core/testing/TestBed#createComponent) does not trigger change detection synchronously; a fact confirmed in the revised test:

```
it('no title in the DOM after createComponent()', () => {
  expect(h1.textContent).toEqual('');
});
```

### [`whenStable()`](#whenstable)

You can tell the [`TestBed`](/api/core/testing/TestBed) to wait for change detection to run with `await fixture.whenStable()`. Only then does the `<h1>` have the expected title.

```
it('should display original title', async () => {
  await fixture.whenStable();
  expect(h1.textContent).toContain(component.title());
});
```

Delayed change detection is intentional and useful. It gives the tester an opportunity to inspect and change the state of the component *before Angular initiates data binding and calls [lifecycle hooks](guide/components/lifecycle)*.

Here's another test that changes the component's `title` property *before* calling `fixture.whenStable()`.

```
it('should display a different test title', async () => {
  component.title.set('Test Title');
  await fixture.whenStable();
  expect(h1.textContent).toContain('Test Title');
});
```

### [Binding signals to inputs](#binding-signals-to-inputs)

To reflect changes to inputs and listen to outputs you can dynamically bind signals to inputs and functions to outputs.

```
import {inputBinding, outputBinding} from '@angular/core';

const fixture = TestBed.createComponent(ValueDisplay, {
  bindings: [
    inputBinding('value', value),
    outputBinding('valueChange', () =>  (/* ... */) ),
  ],
});
```

### [Change an input value with `dispatchEvent()`](#change-an-input-value-with-dispatchevent)

To simulate user input, find the input element and set its `value` property.

But there is an essential, intermediate step.

Angular doesn't know that you set the input element's `value` property. It won't read that property until you raise the element's `input` event by calling `dispatchEvent()`.

The following example of a component using the [`TitleCasePipe`](/api/common/TitleCasePipe) demonstrates the proper sequence.

```
it('should convert hero name to Title Case', async () => {
  const hostElement = fixture.nativeElement;
  const nameInput: HTMLInputElement = hostElement.querySelector('input')!;
  const nameDisplay: HTMLElement = hostElement.querySelector('span')!;

  // simulate user entering a new name into the input box
  nameInput.value = 'quick BROWN  fOx';

  // Dispatch a DOM event so that Angular learns of input value change.
  nameInput.dispatchEvent(new Event('input'));

  // Wait for Angular to update the display binding through the title pipe
  await fixture.whenStable();

  expect(nameDisplay.textContent).toBe('Quick Brown  Fox');
});
```

## [Component with a dependency](#component-with-a-dependency)

Components often have service dependencies.

The `Welcome` component displays a welcome message to the logged-in user. It knows who the user is based on a property of the injected `UserAuthentication`:

```
import {Component, inject, OnInit, signal} from '@angular/core';
import {UserAuthentication} from '../model/user.authentication';

@Component({
  selector: 'app-welcome',
  template: '<h3 class="welcome"><i>{{ welcome() }}</i></h3>',
})
export class Welcome {
  private userAuth = inject(UserAuthentication);
  welcome = signal(
    this.userAuth.isLoggedIn() ? `Welcome, ${this.userAuth.user().name}` : 'Please log in.',
  );
}
```

The `Welcome` component has decision logic that interacts with the service, logic that makes this component worth testing.

### [Provide service test doubles](#provide-service-test-doubles)

A *component-under-test* doesn't have to be provided with real services.

Injecting the real `UserAuthentication` could be difficult. The real service might ask the user for login credentials and attempt to reach an authentication server. These behaviors can be hard to intercept. Be aware that using test doubles makes the test behave differently from production so use them sparingly.

### [Get injected services](#get-injected-services)

The tests need access to the `UserAuthentication` injected into the `Welcome` component.

Angular has a hierarchical injection system. There can be injectors at multiple levels, from the root injector created by the [`TestBed`](/api/core/testing/TestBed) down through the component tree.

The safest way to get the injected service, the way that ***always works***, is to **get it from the injector of the *component-under-test***. The component injector is a property of the fixture's [`DebugElement`](/api/core/DebugElement).

```
// UserAuthentication actually injected into the component
userAuth = fixture.debugElement.injector.get(UserAuthentication);
```

**HELPFUL:** This is *usually* not necessary. Services are often provided in the root or the TestBed overrides and can be retrieved more easily with [`TestBed.inject()`](/api/core/testing/TestBed#inject) (see below).

### [`TestBed.inject()`](#testbedinject)

This is easier to remember and less verbose than retrieving a service using the fixture's [`DebugElement`](/api/core/DebugElement).

In this test suite, the *only* provider of `UserAuthentication` is the root testing module, so it is safe to call [`TestBed.inject()`](/api/core/testing/TestBed#inject) as follows:

```
userAuth = TestBed.inject(UserAuthentication);
```

**HELPFUL:** For a use case in which [`TestBed.inject()`](/api/core/testing/TestBed#inject) does not work, see the [*Override component providers*](#override-component-providers) section that explains when and why you must get the service from the component's injector instead.

### [Final setup and tests](#final-setup-and-tests)

Here's the complete `beforeEach()`, using [`TestBed.inject()`](/api/core/testing/TestBed#inject):

```
let fixture: ComponentFixture<Welcome>;
let comp: Welcome;
let userAuth: UserAuthentication; // the TestBed injected service
let el: HTMLElement; // the DOM element with the welcome message

beforeEach(() => {
  fixture = TestBed.createComponent(Welcome);
  comp = fixture.componentInstance;

  // UserAuthentication from the root injector
  userAuth = TestBed.inject(UserAuthentication);

  //  get the "welcome" element by CSS selector (e.g., by class name)
  el = fixture.nativeElement.querySelector('.welcome');
});
```

And here are some tests:

```
it('should welcome the user', async () => {
  await fixture.whenStable();
  const content = el.textContent;

  expect(content, '"Welcome ..."').toContain('Welcome');
  expect(content, 'expected name').toContain('Test User');
});

it('should welcome "Bubba"', async () => {
  userAuth.user.set({name: 'Bubba'}); // welcome message hasn't been shown yet
  await fixture.whenStable();

  expect(el.textContent).toContain('Bubba');
});

it('should request login if not logged in', async () => {
  userAuth.isLoggedIn.set(false); // welcome message hasn't been shown yet
  await fixture.whenStable();
  const content = el.textContent;

  expect(content, 'not welcomed').not.toContain('Welcome');
  expect(content, '"log in"').toMatch(/log in/i);
});
```

The first is a sanity test; it confirms that the `UserAuthentication` is called and working.

**HELPFUL:** The 2nd argument of `expect` (for example, `'expected name'`) is an optional failure label. If the expectation fails, Vitest appends this label to the expectation failure message. In a spec with multiple expectations, it can help clarify what went wrong and which expectation failed.

The remaining tests confirm the logic of the component when the service returns different values. The second test validates the effect of changing the user name. The third test checks that the component displays the proper message when there is no logged-in user.

## [Component with async service](#component-with-async-service)

In this sample, the `About` component template hosts a `Twain` component. The `Twain` component displays Mark Twain quotes.

```
<p class="twain">
  <i>{{ quote | async }}</i>
</p>
<button type="button" (click)="getQuote()">Next quote</button>
@if (errorMessage()) {
  <p class="error">{{ errorMessage() }}</p>
}
```

**HELPFUL:** The value of the component's `quote` property passes through an [`AsyncPipe`](/api/common/AsyncPipe). That means the property returns either a `Promise` or an `Observable`.

In this example, the `TwainQuotes.getQuote()` method tells you that the `quote` property returns an `Observable`.

```
getQuote() {
  this.errorMessage.set('');
  this.quote = this.twainQuotes.getQuote().pipe(
    startWith('...'),
    catchError((err: any) => {
      this.errorMessage.set(err.message || err.toString());
      return of('...'); // reset message to placeholder
    }),
  );
}
```

The `Twain` component gets quotes from an injected `TwainQuotes`. The component starts the returned `Observable` with a placeholder value (`'...'`), before the service can return its first quote.

The `catchError` intercepts service errors, prepares an error message, and returns the placeholder value on the success channel.

These are all features you'll want to test.

### [Testing by mocking http requests with the `HttpTestingController`.](#testing-by-mocking-http-requests-with-the-httptestingcontroller)

When testing a component, only the service's public API should matter. In general, tests themselves should not make calls to remote servers. They should emulate such calls.

In the case your async service relies on the [`HttpClient`](/api/common/http/HttpClient) to load remote data, it is recommended to return mock responses at the HTTP level with the [`HttpTestingController`](/api/common/http/testing/HttpTestingController).

For more details on mocking the [`HttpBackend`](/api/common/http/HttpBackend), refer to the [dedicated guide](guide/http/testing).

### [Testing by providing a stubbed implementation of a service.](#testing-by-providing-a-stubbed-implementation-of-a-service)

When mocking async request at the http level isn't possible, an alternative is to leverage spies.

The setup in this `app/twain/twain-quotes.spec.ts` shows one way to do that:

### twain.spec.ts

```
class TwainQuotesStub implements TwainQuotes {
  private testQuote = 'Test Quote';

  getQuote() {
    return of(this.testQuote);
  }

  // ... Implement everything to conform to the API
}

beforeEach(async () => {
  TestBed.configureTestingModule({
    providers: [{provide: TwainQuotes, useClass: TwainQuotesStub}],
  });

  fixture = TestBed.createComponent(Twain);
  component = fixture.componentInstance;
  await fixture.whenStable();
  quoteEl = fixture.nativeElement.querySelector('.twain');
});
```

Focus on the how the stub implementation replaces the original one.

```
TestBed.configureTestingModule({
  providers: [{provide: TwainQuotes, useClass: TwainQuotesStub}],
});
```

The stub is designed in such a way that any component or service that injects it will receive the stubbed implementation. It means that any call to `getQuote` receives an observable with a test quote.

Unlike the real `getQuote()` method, this spy bypasses the server and returns a synchronous observable whose value is available immediately.

### [Async test with a Vitest fake timers](#async-test-with-a-vitest-fake-timers)

To mock async functions like `setTimeout` or `Promise`s, you can leverage Vitest fake timers to control whenever they fire.

```
it('should display error when TwainQuotes service fails', async () => {
  class TwainQuotesStub implements TwainQuotes {
    getQuote() {
      return defer(() => {
        return new Promise<string>((_, reject) => {
          setTimeout(() => reject('TwainService test failure'));
        });
      });
    }

    // ... Implement everything to conform to the API
  }

  TestBed.configureTestingModule({
    providers: [{provide: TwainQuotes, useClass: TwainQuotesStub}],
  });

  vi.useFakeTimers(); // setting up the fake timers
  const fixture = TestBed.createComponent(TwainComponent);

  // rendering isn't async, we need to flush
  await vi.runAllTimersAsync();

  await expect(fixture.nativeElement.querySelector('.error')!.textContent).toMatch(/test failure/);
  expect(fixture.nativeElement.querySelector('.twain')!.textContent).toBe('...');

  vi.useRealTimers(); // resets to regular async execution
});
```

### [More async tests](#more-async-tests)

With the stubbed service returning async observables, most of your tests will have to be async as well.

Here's a test that demonstrates the data flow you'd expect in the real world.

```
it('should show quote after getQuote', async () => {
  class MockTwainQuotes implements TwainQuotes {
    private subject = new Subject<string>();

    getQuote() {
      return this.subject.asObservable();
    }

    emit(val: string) {
      this.subject.next(val);
    }
  }

  it('should show quote after getQuote (success)', async () => {
    vi.useFakeTimers();

    TestBed.configureTestingModule({
      providers: [{provide: TwainQuotes, useClass: MockTwainQuotes}],
    });

    const fixture = TestBed.createComponent(TwainComponent);
    const twainQuotes = TestBed.inject(TwainQuotes) as MockTwainQuotes;
    await vi.runAllTimersAsync(); // render before the quote is received

    const quoteEl = fixture.nativeElement.querySelector('.twain');
    expect(quoteEl.textContent).toBe('...');

    twainQuotes.emit('Twain Quote'); // emits the quote
    await vi.runAllTimersAsync(); // render with the quote received

    expect(quoteEl.textContent).toBe('Twain Quote');
    expect(fixture.nativeElement.querySelector('.error')).toBeNull();

    vi.useRealTimers();
  });
});
```

Notice that the quote element displays the placeholder value (`'...'`) on first rendering. The first quote hasn't arrived yet.

Then you can assert that the quote element displays the expected text.

### [Async tests with `zone.js` and `fakeAsync`](#async-tests-with-zonejs-and-fakeasync)

The [`fakeAsync`](/api/core/testing/fakeAsync) helper function is another mock clock that relies on patching asynchronous APIs with `zone.js`. It was commonly used in `zone.js` based applications for testing. The use of [`fakeAsync`](/api/core/testing/fakeAsync) is no longer recommended.

**TIP:** Prefer using native async testing strategies or other fake timers (also called mock clocks) like those from Vitest or Jasmine.

**IMPORTANT:** [`fakeAsync`](/api/core/testing/fakeAsync) requires `zone.js`. To use it with the Vitest test runner, add the `zone.js/plugins/vitest-patch` polyfill as described in [the Vitest migration guide](guide/testing/migrating-to-vitest#zonejs-vitest-patch).

## [Component with inputs and outputs](#component-with-inputs-and-outputs)

A component with inputs and outputs typically appears inside the view template of a host component. The host uses a property binding to set the input property and an event binding to listen to events raised by the output property.

The testing goal is to verify that such bindings work as expected. The tests should set input values and listen for output events.

The `DashboardHero` component is a tiny example of a component in this role. It displays an individual hero provided by the `Dashboard` component. Clicking that hero tells the `Dashboard` component that the user has selected the hero.

The `DashboardHero` component is embedded in the `Dashboard` component template like this:

```
@for (hero of heroes; track hero) {
  <dashboard-hero class="col-1-4" [hero]="hero" (selected)="gotoDetail($event)" />
}
```

The `DashboardHero` component appears in an `@for` block, which sets each component's `hero` input property to the looping value and listens for the component's `selected` event.

Here's the component's full definition:

```
@Component({
  selector: 'dashboard-hero',
  imports: [UpperCasePipe],
  template: `
    <button type="button" (click)="click()" class="hero">
      {{ hero().name | uppercase }}
    </button>
  `,
})
export class DashboardHero {
  readonly hero = input.required<Hero>();
  readonly selected = output<Hero>();

  click() {
    this.selected.emit(this.hero());
  }
}
```

While testing a component this simple has little intrinsic value, it's worth knowing how. Use one of these approaches:

* Test it as used by the `Dashboard` component
* Test it as a standalone component
* Test it as used by a substitute for the `Dashboard` component

The immediate goal is to test the `DashboardHero` component, not the `Dashboard` component, so, try the second and third options.

### [Test the `DashboardHero` component standalone](#test-the-dashboardhero-component-standalone)

Here's the meat of the spec file setup.

```
let fixture: ComponentFixture<DashboardHero>;
let comp: DashboardHero;
let heroDe: DebugElement;
let heroEl: HTMLElement;
let expectedHero: Hero;

beforeEach(async () => {
  fixture = TestBed.createComponent(DashboardHero);
  comp = fixture.componentInstance;

  // find the hero's DebugElement and element
  heroDe = fixture.debugElement.query(By.css('.hero'));
  heroEl = heroDe.nativeElement;

  // mock the hero supplied by the parent component
  expectedHero = {id: 42, name: 'Test Name'};

  // simulate the parent setting the input property with that hero
  fixture.componentRef.setInput('hero', expectedHero);

  // wait for initial data binding
  await fixture.whenStable();
});
```

Notice how the setup code assigns a test hero (`expectedHero`) to the component's `hero` property, emulating the way the `Dashboard` would set it using the property binding in its repeater.

The following test verifies that the hero name is propagated to the template using a binding.

```
it('should display hero name in uppercase', () => {
  const expectedPipedName = expectedHero.name.toUpperCase();
  expect(heroEl.textContent).toContain(expectedPipedName);
});
```

Because the template passes the hero name through the Angular [`UpperCasePipe`](/api/common/UpperCasePipe), the test must match the element value with the upper-cased name.

### [Clicking](#clicking)

Clicking the hero should raise a `selected` event that the host component (`Dashboard` presumably) can hear:

```
it('should raise selected event when clicked (triggerEventHandler)', () => {
  let selectedHero: Hero | undefined;
  comp.selected.subscribe((hero: Hero) => (selectedHero = hero));

  heroDe.triggerEventHandler('click');
  expect(selectedHero).toBe(expectedHero);
});
```

The component's `selected` property is an [`OutputEmitterRef`](/api/core/OutputEmitterRef), whose `subscribe` method notifies subscribers synchronously. The test subscribes to it *explicitly* just as the host component does *implicitly*.

If the component behaves as expected, clicking the hero's element should tell the component's `selected` property to emit the `hero` object.

The test detects that event through its subscription to `selected`.

### [`triggerEventHandler`](#triggereventhandler)

The `heroDe` in the previous test is a [`DebugElement`](/api/core/DebugElement) that represents the hero `<button>`.

It has Angular properties and methods that abstract interaction with the native element. This test calls the [`DebugElement.triggerEventHandler`](/api/core/DebugElement#triggerEventHandler) with the "click" event name. The "click" event binding responds by calling `DashboardHero.click()`.

The Angular [`DebugElement.triggerEventHandler`](/api/core/DebugElement#triggerEventHandler) can raise *any data-bound event* by its *event name*. The second parameter is the event object passed to the handler.

The test triggered a "click" event.

```
heroDe.triggerEventHandler('click');
```

In this case, the test correctly assumes that the runtime event handler, the component's `click()` method, doesn't care about the event object.

**HELPFUL:** Other handlers are less forgiving. For example, the [`RouterLink`](/api/router/RouterLink) directive expects an object with a `button` property that identifies which mouse button, if any, was pressed during the click. The [`RouterLink`](/api/router/RouterLink) directive throws an error if the event object is missing.

### [Click the element](#click-the-element)

The following test alternative calls the native element's own `click()` method, which is perfectly fine for *this component*.

```
it('should raise selected event when clicked (element.click)', () => {
  let selectedHero: Hero | undefined;
  comp.selected.subscribe((hero: Hero) => (selectedHero = hero));

  heroEl.click();
  expect(selectedHero).toBe(expectedHero);
});
```

### [`click()` helper](#click-helper)

Clicking a button, an anchor, or an arbitrary HTML element is a common test task.

Make that consistent and straightforward by encapsulating the *click-triggering* process in a helper such as the following `click()` function:

```
/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */
export const ButtonClickEvents = {
  left: {button: 0},
  right: {button: 2},
};

/** Simulate element click. Defaults to mouse left-button click event. */
export function click(
  el: DebugElement | HTMLElement,
  eventObj: any = ButtonClickEvents.left,
): void {
  if (el instanceof HTMLElement) {
    el.click();
  } else {
    el.triggerEventHandler('click', eventObj);
  }
}
```

The first parameter is the *element-to-click*. If you want, pass a custom event object as the second parameter. The default is a partial [left-button mouse event object](https://developer.mozilla.org/docs/Web/API/MouseEvent/button) accepted by many handlers including the [`RouterLink`](/api/router/RouterLink) directive.

**IMPORTANT:** The `click()` helper function is **not** one of the Angular testing utilities. It's a function defined in *this guide's sample code*. All of the sample tests use it. If you like it, add it to your own collection of helpers.

Here's the previous test, rewritten using the click helper.

```
it('should raise selected event when clicked (click helper with DebugElement)', () => {
  let selectedHero: Hero | undefined;
  comp.selected.subscribe((hero: Hero) => (selectedHero = hero));

  click(heroDe); // click helper with DebugElement

  expect(selectedHero).toBe(expectedHero);
});
```

## [Component inside a test host](#component-inside-a-test-host)

The previous tests played the role of the host `Dashboard` component themselves. But does the `DashboardHero` component work correctly when properly data-bound to a host component?

```
@Component({
  imports: [DashboardHero],
  template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)" />`,
})
class TestHost {
  hero: Hero = {id: 42, name: 'Test Name'};
  selectedHero: Hero | undefined;

  onSelected(hero: Hero) {
    this.selectedHero = hero;
  }
}
```

The test host sets the component's `hero` input property with its test hero. It binds the component's `selected` event with its `onSelected` handler, which records the emitted hero in its `selectedHero` property.

Later, the tests will be able to check `selectedHero` to verify that the `DashboardHero.selected` event emitted the expected hero.

The setup for the `test-host` tests is similar to the setup for the stand-alone tests:

```
beforeEach(async () => {
  // create TestHost instead of DashboardHero
  fixture = TestBed.createComponent(TestHost);
  testHost = fixture.componentInstance;
  heroEl = fixture.nativeElement.querySelector('.hero');

  await fixture.whenStable();
});
```

This testing module configuration shows two important differences:

* It *creates* the `TestHost` component instead of the `DashboardHero`
* The `TestHost` component sets the `DashboardHero.hero` with a binding

The [`createComponent`](/api/core/createComponent) returns a `fixture` that holds an instance of `TestHost` instead of an instance of `DashboardHero`.

Creating the `TestHost` has the side effect of creating a `DashboardHero` because the latter appears within the template of the former. The query for the hero element (`heroEl`) still finds it in the test DOM, albeit at greater depth in the element tree than before.

The tests themselves are almost identical to the stand-alone version:

```
it('should display hero name', () => {
  const expectedPipedName = testHost.hero.name.toUpperCase();
  expect(heroEl.textContent).toContain(expectedPipedName);
});

it('should raise selected event when clicked', () => {
  click(heroEl);
  // selected hero should be the same data bound hero
  expect(testHost.selectedHero).toBe(testHost.hero);
});
```

Only the selected event test differs. It confirms that the selected `DashboardHero` hero really does find its way up through the event binding to the host component.

## [Routing component](#routing-component)

A *routing component* is a component that tells the [`Router`](/api/router/Router) to navigate to another component. The `Dashboard` component is a *routing component* because the user can navigate to the `HeroDetail` component by clicking on one of the *hero buttons* on the dashboard.

Angular provides test helpers to reduce boilerplate and more effectively test code which depends on [`HttpClient`](/api/common/http/HttpClient). The [`provideRouter`](/api/router/provideRouter) function can be used directly in the test module as well.

```
beforeEach(async () => {
  TestBed.configureTestingModule({
    providers: [
      provideRouter([{path: '**', component: Dashboard}]),
      provideHttpClientTesting(),
      HeroService,
    ],
  });
  harness = await RouterTestingHarness.create();
  comp = await harness.navigateByUrl('/', Dashboard);
  TestBed.inject(HttpTestingController).expectOne('api/heroes').flush(getTestHeroes());
});
```

The following test clicks the displayed hero and confirms that we navigate to the expected URL.

```
it('should tell navigate when hero clicked', async () => {
  // get first <dashboard-hero> DebugElement
  const heroDe = harness.routeDebugElement!.query(By.css('dashboard-hero'));
  heroDe.triggerEventHandler('selected', comp.heroes[0]);

  // expecting to navigate to id of the component's first hero
  const id = comp.heroes[0].id;
  expect(TestBed.inject(Router).url, 'should nav to HeroDetail for first hero').toEqual(
    `/heroes/${id}`,
  );
});
```

## [Routed components](#routed-components)

A *routed component* is the destination of a [`Router`](/api/router/Router) navigation. It can be trickier to test, especially when the route to the component *includes parameters*. The `HeroDetail` is a *routed component* that is the destination of such a route.

When a user clicks a *Dashboard* hero, the `Dashboard` tells the [`Router`](/api/router/Router) to navigate to `heroes/:id`. The `:id` is a route parameter whose value is the `id` of the hero to edit.

The [`Router`](/api/router/Router) matches that URL to a route to the `HeroDetail`. It creates an [`ActivatedRoute`](/api/router/ActivatedRoute) object with the routing information and injects it into a new instance of the `HeroDetail`.

Here are the services injected into `HeroDetail`:

```
private heroDetailService = inject(HeroDetailService);
private route = inject(ActivatedRoute);
private router = inject(Router);
```

The `HeroDetail` component needs the `id` parameter so it can fetch the corresponding hero using the `HeroDetailService`. The component has to get the `id` from the [`ActivatedRoute.paramMap`](/api/router/ActivatedRoute#paramMap) property which is an `Observable`.

It can't just reference the `id` property of the [`ActivatedRoute.paramMap`](/api/router/ActivatedRoute#paramMap). The component has to *subscribe* to the [`ActivatedRoute.paramMap`](/api/router/ActivatedRoute#paramMap) observable and be prepared for the `id` to change during its lifetime.

```
constructor() {
  // get hero when `id` param changes
  this.route.paramMap.subscribe((pmap) => this.getHero(pmap.get('id')));
}
```

Tests can explore how the `HeroDetail` responds to different `id` parameter values by navigating to different routes.

## [Nested component tests](#nested-component-tests)

Component templates often have nested components, whose templates might contain more components.

The component tree can be very deep and sometimes the nested components play no role in testing the component at the top of the tree.

The `App` component, for example, displays a navigation bar with anchors and their [`RouterLink`](/api/router/RouterLink) directives.

```
<app-banner />
<app-welcome />

<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
  <a routerLink="/about">About</a>
</nav>

<router-outlet />
```

To validate the links but not the navigation, you don't need the [`Router`](/api/router/Router) to navigate and you don't need the `<router-outlet>` to mark where the [`Router`](/api/router/Router) inserts *routed components*.

The `Banner` and `Welcome` components (indicated by `<app-banner>` and `<app-welcome>`) are also irrelevant.

Yet any test that creates the `App` component in the DOM also creates instances of these three components and, if you let that happen, you'll have to configure the [`TestBed`](/api/core/testing/TestBed) to create them.

If you neglect to declare them, the Angular compiler won't recognize the `<app-banner>`, `<app-welcome>`, and `<router-outlet>` tags in the `App` template and will throw an error.

If you declare the real components, you'll also have to declare *their* nested components and provide for *all* services injected in *any* component in the tree.

This section describes two techniques for minimizing the setup. Use them, alone or in combination, to stay focused on testing the primary component.

### [Stubbing unneeded components](#stubbing-unneeded-components)

In the first technique, you create and declare stub versions of the components and directive that play little or no role in the tests.

```
@Component({selector: 'app-banner', template: ''})
class BannerStub {}

@Component({selector: 'router-outlet', template: ''})
class RouterOutletStub {}

@Component({selector: 'app-welcome', template: ''})
class WelcomeStub {}
```

The stub selectors match the selectors for the corresponding real components. But their templates and classes are empty.

Then declare them by overriding the `imports` of your component using [`TestBed.overrideComponent`](/api/core/testing/TestBed#overrideComponent).

```
let comp: App;
let fixture: ComponentFixture<App>;

beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [provideRouter([]), UserAuthentication],
  }).overrideComponent(App, {
    set: {
      imports: [RouterLink, BannerStub, RouterOutletStub, WelcomeStub],
    },
  });

  fixture = TestBed.createComponent(App);
  comp = fixture.componentInstance;
});
```

**HELPFUL:** The `set` key in this example replaces all the existing imports on your component, make sure to import all dependencies, not only the stubs. Alternatively you can use the `remove`/`add` keys to selectively remove and add imports.

### [`NO_ERRORS_SCHEMA`](#noerrorsschema)

In the second approach, add [`NO_ERRORS_SCHEMA`](/api/core/NO_ERRORS_SCHEMA) to the metadata overrides of your component.

```
beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [provideRouter([]), UserAuthentication],
  }).overrideComponent(App, {
    set: {
      imports: [], // resets all imports
      schemas: [NO_ERRORS_SCHEMA],
    },
  });
});
```

The [`NO_ERRORS_SCHEMA`](/api/core/NO_ERRORS_SCHEMA) tells the Angular compiler to ignore unrecognized elements and attributes.

The compiler recognizes the `<app-root>` element and the [`routerLink`](/api/router/RouterLinkWithHref) attribute because you declared a corresponding `App` component and [`RouterLink`](/api/router/RouterLink) in the [`TestBed`](/api/core/testing/TestBed) configuration.

But the compiler won't throw an error when it encounters `<app-banner>`, `<app-welcome>`, or `<router-outlet>`. It simply renders them as empty tags and the browser ignores them.

You no longer need the stub components.

### [Use both techniques together](#use-both-techniques-together)

These are techniques for *Shallow Component Testing*, so-named because they reduce the visual surface of the component to just those elements in the component's template that matter for tests.

The [`NO_ERRORS_SCHEMA`](/api/core/NO_ERRORS_SCHEMA) approach is the easier of the two but don't overuse it.

The [`NO_ERRORS_SCHEMA`](/api/core/NO_ERRORS_SCHEMA) also prevents the compiler from telling you about the missing components and attributes that you omitted inadvertently or misspelled. You could waste hours chasing phantom bugs that the compiler would have caught in an instant.

The *stub component* approach has another advantage. While the stubs in *this* example were empty, you could give them stripped-down templates and classes if your tests need to interact with them in some way.

In practice you will combine the two techniques in the same setup, as seen in this example.

```
beforeEach(() => {
  TestBed.configureTestingModule({
    providers: [provideRouter([]), UserAuthentication],
  }).overrideComponent(App, {
    remove: {imports: [RouterOutlet, Welcome]},
    set: {schemas: [NO_ERRORS_SCHEMA]},
  });
});
```

The Angular compiler creates the `BannerStub` for the `<app-banner>` element and applies the [`RouterLink`](/api/router/RouterLink) to the anchors with the [`routerLink`](/api/router/RouterLinkWithHref) attribute, but it ignores the `<app-welcome>` and `<router-outlet>` tags.

### [`By.directive` and injected directives](#bydirective-and-injected-directives)

A little more setup triggers the initial data binding and gets references to the navigation links:

```
beforeEach(async () => {
  await fixture.whenStable();

  // find DebugElements with an attached RouterLinkStubDirective
  linkDes = fixture.debugElement.queryAll(By.directive(RouterLink));

  // get attached link directive instances
  // using each DebugElement's injector
  routerLinks = linkDes.map((de) => de.injector.get(RouterLink));
});
```

Three points of special interest:

* Locate the anchor elements with an attached directive using [`By.directive`](/api/platform-browser/By#directive)
* The query returns [`DebugElement`](/api/core/DebugElement) wrappers around the matching elements
* Each [`DebugElement`](/api/core/DebugElement) exposes a dependency injector with the specific instance of the directive attached to that element

The `App` component links to validate are as follows:

```
<nav>
  <a routerLink="/dashboard">Dashboard</a>
  <a routerLink="/heroes">Heroes</a>
  <a routerLink="/about">About</a>
</nav>
```

Here are some tests that confirm those links are wired to the [`routerLink`](/api/router/RouterLinkWithHref) directives as expected:

```
it('can get RouterLinks from template', () => {
  expect(routerLinks.length, 'should have 3 routerLinks').toBe(3);
  expect(routerLinks[0].href).toBe('/dashboard');
  expect(routerLinks[1].href).toBe('/heroes');
  expect(routerLinks[2].href).toBe('/about');
});

it('can click Heroes link in template', async () => {
  const heroesLinkDe = linkDes[1]; // heroes link DebugElement

  TestBed.inject(Router).resetConfig([{path: '**', children: []}]);
  heroesLinkDe.triggerEventHandler('click', {button: 0});

  await fixture.whenStable();

  expect(TestBed.inject(Router).url).toBe('/heroes');
});
```

## [Use a `page` object](#use-a-page-object)

The `HeroDetail` component is a simple view with a title, two hero fields, and two buttons.

But there's plenty of template complexity even in this simple form.

```
@if (hero) {
  <div>
    <h2>
      <span>{{ hero.name | titlecase }}</span> Details
    </h2>
    <div><span>id: </span>{{ hero.id }}</div>
    <div>
      <label for="name">name: </label>
      <input id="name" [(ngModel)]="hero.name" placeholder="name" />
    </div>
    <button type="button" (click)="save()">Save</button>
    <button type="button" (click)="cancel()">Cancel</button>
  </div>
}
```

Tests that exercise the component need …

* To wait until a hero arrives before elements appear in the DOM
* A reference to the title text
* A reference to the name input box to inspect and set it
* References to the two buttons so they can click them

Even a small form such as this one can produce a mess of tortured conditional setup and CSS element selection.

Tame the complexity with a `Page` class that handles access to component properties and encapsulates the logic that sets them.

Here is such a `Page` class for the `hero-detail.component.spec.ts`

```
class Page {
  // getter properties wait to query the DOM until called.
  get buttons() {
    return this.queryAll<HTMLButtonElement>('button');
  }
  get saveBtn() {
    return this.buttons[0];
  }
  get cancelBtn() {
    return this.buttons[1];
  }
  get nameDisplay() {
    return this.query<HTMLElement>('span');
  }
  get nameInput() {
    return this.query<HTMLInputElement>('input');
  }

  //// query helpers ////
  private query<T>(selector: string): T {
    return harness.routeNativeElement!.querySelector(selector)! as T;
  }

  private queryAll<T>(selector: string): T[] {
    return harness.routeNativeElement!.querySelectorAll(selector) as any as T[];
  }
}
```

Now the important hooks for component manipulation and inspection are neatly organized and accessible from an instance of `Page`.

A [`createComponent`](/api/core/createComponent) method creates a `page` object and fills in the blanks once the `hero` arrives.

```
async function createComponent(id: number) {
  harness = await RouterTestingHarness.create();
  component = await harness.navigateByUrl(`/heroes/${id}`, HeroDetail);
  page = new Page();

  const request = TestBed.inject(HttpTestingController).expectOne(`api/heroes/?id=${id}`);
  const hero = getTestHeroes().find((h) => h.id === Number(id));
  request.flush(hero ? [hero] : []);
  await harness.fixture.whenStable();
}
```

Here are a few more `HeroDetail` component tests to reinforce the point.

```
it("should display that hero's name", () => {
  expect(page.nameDisplay.textContent).toBe(expectedHero.name);
});

it('should navigate when click cancel', () => {
  click(page.cancelBtn);
  expect(TestBed.inject(Router).url).toEqual(`/heroes/${expectedHero.id}`);
});

it('should save when click save but not navigate immediately', () => {
  click(page.saveBtn);
  expect(TestBed.inject(HttpTestingController).expectOne({method: 'PUT', url: 'api/heroes'}));
  expect(TestBed.inject(Router).url).toEqual('/heroes/41');
});

it('should navigate when click save and save resolves', async () => {
  click(page.saveBtn);
  await harness.fixture.whenStable();
  expect(TestBed.inject(Router).url).toEqual('/heroes/41');
});

it('should convert hero name to Title Case', async () => {
  // get the name's input and display elements from the DOM
  const hostElement: HTMLElement = harness.routeNativeElement!;
  const nameInput: HTMLInputElement = hostElement.querySelector('input')!;
  const nameDisplay: HTMLElement = hostElement.querySelector('span')!;

  // simulate user entering a new name into the input box
  nameInput.value = 'quick BROWN  fOx';

  // Dispatch a DOM event so that Angular learns of input value change.
  nameInput.dispatchEvent(new Event('input'));

  // Wait for Angular to update the display binding through the title pipe
  await harness.fixture.whenStable();

  expect(nameDisplay.textContent).toBe('Quick Brown  Fox');
});
```

## [Override component providers](#override-component-providers)

The `HeroDetail` provides its own `HeroDetailService`.

```
@Component({
  /* ... */
  providers: [HeroDetailService],
})
export class HeroDetail {
  private heroDetailService = inject(HeroDetailService);
  private route = inject(ActivatedRoute);
  private router = inject(Router);
}
```

It's not possible to stub the component's `HeroDetailService` in the `providers` of the [`TestBed.configureTestingModule`](/api/core/testing/TestBed#configureTestingModule). Those are providers for the *testing module*, not the component. They prepare the dependency injector at the *fixture level*.

Angular creates the component with its *own* injector, which is a *child* of the fixture injector. It registers the component's providers (the `HeroDetailService` in this case) with the child injector.

A test cannot get to child injector services from the fixture injector. And [`TestBed.configureTestingModule`](/api/core/testing/TestBed#configureTestingModule) can't configure them either.

Angular has created new instances of the real `HeroDetailService` all along!

**HELPFUL:** These tests could fail or timeout if the `HeroDetailService` made its own XHR calls to a remote server. There might not be a remote server to call.

Fortunately, the `HeroDetailService` delegates responsibility for remote data access to an injected `HeroService`.

```
@Service()
export class HeroDetailService {
  private heroService = inject(HeroService);
}
```

The previous test configuration replaces the real `HeroService` with a `TestHeroService` that intercepts server requests and fakes their responses.

What if you aren't so lucky. What if faking the `HeroService` is hard? What if `HeroDetailService` makes its own server requests?

The [`TestBed.overrideComponent`](/api/core/testing/TestBed#overrideComponent) method can replace the component's `providers` with easy-to-manage *test doubles* as seen in the following setup variation:

```
beforeEach(async () => {
  await TestBed.configureTestingModule({
    providers: [
      provideRouter([
        {path: 'heroes', component: HeroList},
        {path: 'heroes/:id', component: HeroDetail},
      ]),
      // HeroDetailService at this level is IRRELEVANT!
      {provide: HeroDetailService, useValue: {}},
    ],
  }).overrideComponent(HeroDetail, {
    set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]},
  });
});
```

Notice that [`TestBed.configureTestingModule`](/api/core/testing/TestBed#configureTestingModule) no longer provides a fake `HeroService` because it's [not needed](#provide-a-spy-stub-herodetailservicespy).

### [The `overrideComponent` method](#the-overridecomponent-method)

Focus on the `overrideComponent` method.

```
.overrideComponent(HeroDetail, {
  set: {providers: [{provide: HeroDetailService, useClass: HeroDetailServiceSpy}]},
});
```

It takes two arguments: the component type to override (`HeroDetail`) and an override metadata object. The [override metadata object](/guide/testing/utility-apis#testbed-class-summary) is a generic defined as follows:

```
type MetadataOverride<T> = {
  add?: Partial<T>;
  remove?: Partial<T>;
  set?: Partial<T>;
};
```

A metadata override object can either add-and-remove elements in metadata properties or completely reset those properties. This example resets the component's `providers` metadata.

The type parameter, `T`, is the kind of metadata you'd pass to the [`@Component`](/api/core/Component) decorator:

```
selector?: string;
template?: string;
templateUrl?: string;
providers?: any[];
…
```

### [Provide a *spy stub* (`HeroDetailServiceSpy`)](#provide-a-spy-stub-herodetailservicespy)

This example completely replaces the component's `providers` array with a new array containing a `HeroDetailServiceSpy`.

The `HeroDetailServiceSpy` is a stubbed version of the real `HeroDetailService` that fakes all necessary features of that service. It neither injects nor delegates to the lower level `HeroService` so there's no need to provide a test double for that.

The related `HeroDetail` component tests will assert that methods of the `HeroDetailService` were called by spying on the service methods. Accordingly, the stub implements its methods as spies:

```
import {vi} from 'vitest';

class HeroDetailServiceSpy {
  testHero: Hero = {...testHero};

  /* emit cloned test hero */
  getHero = vi.fn(() => asyncData({...this.testHero}));

  /* emit clone of test hero, with changes merged in */
  saveHero = vi.fn((hero: Hero) => asyncData(Object.assign(this.testHero, hero)));
}
```

### [The override tests](#the-override-tests)

Now the tests can control the component's hero directly by manipulating the spy-stub's `testHero` and confirm that service methods were called.

```
let hdsSpy: HeroDetailServiceSpy;

beforeEach(async () => {
  harness = await RouterTestingHarness.create();
  component = await harness.navigateByUrl(`/heroes/${testHero.id}`, HeroDetail);
  page = new Page();
  // get the component's injected HeroDetailServiceSpy
  hdsSpy = harness.routeDebugElement!.injector.get(HeroDetailService) as any;

  harness.detectChanges();
});

it('should have called `getHero`', () => {
  expect(hdsSpy.getHero, 'getHero called once').toHaveBeenCalledTimes(1);
});

it("should display stub hero's name", () => {
  expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name);
});

it('should save stub hero change', async () => {
  const origName = hdsSpy.testHero.name;
  const newName = 'New Name';

  page.nameInput.value = newName;

  page.nameInput.dispatchEvent(new Event('input')); // tell Angular

  expect(component.hero.name, 'component hero has new name').toBe(newName);
  expect(hdsSpy.testHero.name, 'service hero unchanged before save').toBe(origName);

  click(page.saveBtn);
  expect(hdsSpy.saveHero, 'saveHero called once').toHaveBeenCalledTimes(1);

  await harness.fixture.whenStable();
  expect(hdsSpy.testHero.name, 'service hero has new name after save').toBe(newName);
  expect(TestBed.inject(Router).url).toEqual('/heroes');
});
```

### [More overrides](#more-overrides)

The [`TestBed.overrideComponent`](/api/core/testing/TestBed#overrideComponent) method can be called multiple times for the same or different components. The [`TestBed`](/api/core/testing/TestBed) offers similar `overrideDirective`, `overrideModule`, and `overridePipe` methods for digging into and replacing parts of these other classes.

Explore the options and combinations on your own.

----
url: https://angular.dev/tutorials/signal-forms/1-set-up-form-model
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/2-connect-form-template)

[](/tutorials/signal-forms/2-connect-form-template)

Every Signal Form starts with a form data model - a signal that defines the shape of your data, and stores your form data.

In this lesson, you'll learn how to:

* Define a TypeScript interface for your form data
* Create a signal to hold form values
* Use the [`form()`](/api/forms/signals/form) function to create a Signal Form

Let's build the foundation for our login form!

***

1. ### [Define the LoginData interface](#define-the-logindata-interface)

   Create a TypeScript interface that defines the structure of your login form data. The form will have:

   * An `email` field (string)
   * A `password` field (string)
   * A `rememberMe` field (boolean)

   ```
   interface LoginData {
     email: string;
     password: string;
     rememberMe: boolean;
   }
   ```

   Add this interface above the [`@Component`](/api/core/Component) decorator.

2. ### [Import signal and form](#import-signal-and-form)

   Import the [`signal`](/api/core/signal) function from `@angular/core` and the `form` function from `@angular/forms/signals`:

   ```
   import {Component, signal} from '@angular/core';
   import {form} from '@angular/forms/signals';
   ```

3. ### [Create the form model signal](#create-the-form-model-signal)

   In your component class, create a `loginModel` signal with initial values. Use the `LoginData` interface as the type parameter:

   ```
   loginModel = signal<LoginData>({
     email: '',
     password: '',
     rememberMe: false,
   });
   ```

   The initial values start as empty strings for text fields and `false` for the checkbox.

4. ### [Create the form](#create-the-form)

   Now create the form by passing your model signal to the [`form()`](/api/forms/signals/form) function:

   ```
   loginForm = form(this.loginModel);
   ```

   The [`form()`](/api/forms/signals/form) function creates a form from your model, giving you access to field state and validation.

Excellent! You've set up your form model. The `loginModel` signal holds your form data, and the `loginForm` provides access to each field with type safety.

Next, you'll learn [how to connect your form to the template](/tutorials/signal-forms/2-connect-form-template)!

----
url: https://angular.dev/api/cdk/testing/testbed/TestbedHarnessEnvironment
----

```
    
      class TestbedHarnessEnvironment extends HarnessEnvironment<Element> {}
    
    
```

@paramrawRootElement`Element`

@param\_fixture`any`

@paramoptions`TestbedHarnessEnvironmentOptions | undefined`

@returns`TestbedHarnessEnvironment`

Flushes change detection and async tasks captured in the Angular zone. In most cases it should not be necessary to call this manually. However, there may be some edge cases where it is needed to fully flush animation events.

@returns`Promise<void>`

Waits for all scheduled or running async tasks to complete. This allows harness authors to wait for async tasks outside of the Angular zone.

This only works when Zone.js is present *and* patches are applied to the test framework by `zone.js/testing` (Jasmine and Jest only) or another script.

@returns`Promise<void>`

Gets the root element for the document.

@returns`Element`

Creates a `TestElement` from a raw element.

@paramelement`Element`

@returns`TestElement`

Creates a `HarnessLoader` rooted at the given raw element.

@paramelement`Element`

@returns`HarnessEnvironment<Element>`

Gets a list of all elements matching the given selector under this environment's root element.

@paramselector`string`

@returns`Promise<Element[]>`

Creates a `HarnessLoader` rooted at the given fixture's root element.

@paramfixture`any`

@paramoptions`TestbedHarnessEnvironmentOptions | undefined`

@returns`HarnessLoader`

Creates a `HarnessLoader` at the document root. This can be used if harnesses are located outside of a fixture (e.g. overlays appended to the document body).

@paramfixture`any`

@paramoptions`TestbedHarnessEnvironmentOptions | undefined`

@returns`HarnessLoader`

Gets the native DOM element corresponding to the given TestElement.

@paramel`TestElement`

@returns`Element`

Creates an instance of the given harness type, using the fixture's root element as the harness's host element. This method should be used when creating a harness for the root element of a fixture, as components do not have the correct selector when they are created as the root of the fixture.

@paramfixture`any`

@paramharnessType`ComponentHarnessConstructor<T>`

@paramoptions`TestbedHarnessEnvironmentOptions | undefined`

@returns`Promise<T>`

Jump to details

----
url: https://angular.dev/api/core/DOCUMENT
----

```
    
      const DOCUMENT: InjectionToken<Document>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/SetDisabledStateOption
----

```
    
      type SetDisabledStateOption = 'whenDisabledForLegacyCode' | 'always'
    
    
```

Jump to details

----
url: https://angular.dev/guide/http/testing
----

As for any external dependency, you must mock the HTTP backend so your tests can simulate interaction with a remote server. The `@angular/common/http/testing` library provides tools to capture requests made by the application, make assertions about them, and mock the responses to emulate your backend's behavior.

The testing library is designed for a pattern where the app executes code and makes requests first. The test then expects that certain requests have or have not been made, performs assertions against those requests, and finally provides responses by "flushing" each expected request.

Finally, tests can verify that the app made no unexpected requests.

## [Setup for testing](/guide/http/testing#setup-for-testing)

To begin testing usage of [`HttpClient`](/api/common/http/HttpClient), configure [`TestBed`](/api/core/testing/TestBed) and include [`provideHttpClientTesting()`](/api/common/http/testing/provideHttpClientTesting) in your test's setup. [`HttpClient`](/api/common/http/HttpClient) is provided by Angular's test environment, and [`provideHttpClientTesting()`](/api/common/http/testing/provideHttpClientTesting) configures it to use a test backend instead of the real network. It also provides [`HttpTestingController`](/api/common/http/testing/HttpTestingController), which you'll use to interact with the test backend, set expectations about which requests have been made, and flush responses to those requests. [`HttpTestingController`](/api/common/http/testing/HttpTestingController) can be injected from [`TestBed`](/api/core/testing/TestBed) once configured.

```
TestBed.configureTestingModule({
  providers: [
    // ... other test providers
    provideHttpClientTesting(),
  ],
});

const httpTesting = TestBed.inject(HttpTestingController);
```

Now when your tests make requests, they will hit the testing backend instead of the normal one. You can use `httpTesting` to make assertions about those requests.

### [Configuring `HttpClient` in tests](/guide/http/testing#configuring-httpclient-in-tests)

If a test needs to configure [`HttpClient`](/api/common/http/HttpClient) features, such as interceptors, include `provideHttpClient(...)` before [`provideHttpClientTesting()`](/api/common/http/testing/provideHttpClientTesting).

**IMPORTANT:** Keep in mind to provide [`provideHttpClient()`](/api/common/http/provideHttpClient) **before** [`provideHttpClientTesting()`](/api/common/http/testing/provideHttpClientTesting), as [`provideHttpClientTesting()`](/api/common/http/testing/provideHttpClientTesting) will overwrite parts of [`provideHttpClient()`](/api/common/http/provideHttpClient). Doing it the other way around can potentially break your tests.

```
TestBed.configureTestingModule({
  providers: [provideHttpClient(withInterceptors([authInterceptor])), provideHttpClientTesting()],
});
```

## [Expecting and answering requests](/guide/http/testing#expecting-and-answering-requests)

For example, you can write a test that expects a GET request to occur and provides a mock response:

```
TestBed.configureTestingModule({
  providers: [ConfigService, provideHttpClientTesting()],
});

const httpTesting = TestBed.inject(HttpTestingController);

// Load `ConfigService` and request the current configuration.
const service = TestBed.inject(ConfigService);
const config$ = service.getConfig<Config>();

// `firstValueFrom` subscribes to the `Observable`, which makes the HTTP request,
// and creates a `Promise` of the response.
const configPromise = firstValueFrom(config$);

// At this point, the request is pending, and we can assert it was made
// via the `HttpTestingController`:
const req = httpTesting.expectOne('/api/config', 'Request to load the configuration');

// We can assert various properties of the request if desired.
expect(req.request.method).toBe('GET');

// Flushing the request causes it to complete, delivering the result.
req.flush(DEFAULT_CONFIG);

// We can then assert that the response was successfully delivered by the `ConfigService`:
expect(await configPromise).toEqual(DEFAULT_CONFIG);

// Finally, we can assert that no other requests were made.
httpTesting.verify();
```

**NOTE:** `expectOne` will fail if the test has made more than one request which matches the given criteria.

As an alternative to asserting on `req.method`, you could instead use an expanded form of `expectOne` to also match the request method:

```
const req = httpTesting.expectOne(
  {
    method: 'GET',
    url: '/api/config',
  },
  'Request to load the configuration',
);
```

**HELPFUL:** The expectation APIs match against the full URL of requests, including any query parameters.

The last step, verifying that no requests remain outstanding, is common enough for you to move it into an `afterEach()` step:

```
afterEach(() => {
  // Verify that none of the tests make any extra HTTP requests.
  TestBed.inject(HttpTestingController).verify();
});
```

## [Handling more than one request at once](/guide/http/testing#handling-more-than-one-request-at-once)

If you need to respond to duplicate requests in your test, use the `match()` API instead of `expectOne()`. It takes the same arguments but returns an array of matching requests. Once returned, these requests are removed from future matching and you are responsible for flushing and verifying them.

```
const allGetRequests = httpTesting.match({method: 'GET'});
for (const req of allGetRequests) {
  // Handle responding to each request.
}
```

## [Advanced matching](/guide/http/testing#advanced-matching)

All matching functions accept a predicate function for custom matching logic:

```
// Look for one request that has a request body.
const requestsWithBody = httpTesting.expectOne((req) => req.body !== null);
```

The `expectNone` function asserts that no requests match the given criteria.

```
// Assert that no mutation requests have been issued.
httpTesting.expectNone((req) => req.method !== 'GET');
```

## [Testing error handling](/guide/http/testing#testing-error-handling)

You should test your app's responses when HTTP requests fail.

### [Backend errors](/guide/http/testing#backend-errors)

To test handling of backend errors (when the server returns a non-successful status code), flush requests with an error response that emulates what your backend would return when a request fails.

```
const req = httpTesting.expectOne('/api/config');
req.flush('Failed!', {status: 500, statusText: 'Internal Server Error'});

// Assert that the application successfully handled the backend error.
```

### [Network errors](/guide/http/testing#network-errors)

Requests can also fail due to network errors, which surface as `ProgressEvent` errors. These can be delivered with the `error()` method:

```
const req = httpTesting.expectOne('/api/config');
req.error(new ProgressEvent('network error!'));

// Assert that the application successfully handled the network error.
```

## [Testing an Interceptor](/guide/http/testing#testing-an-interceptor)

You should test that your interceptors work under the desired circumstances.

For example, an application may be required to add an authentication token generated by a service to each outgoing request. This behavior can be enforced with the use of an interceptor:

```
export function authInterceptor(
  request: HttpRequest<unknown>,
  next: HttpHandlerFn,
): Observable<HttpEvent<unknown>> {
  const authService = inject(AuthService);

  const clonedRequest = request.clone({
    headers: request.headers.append('X-Authentication-Token', authService.getAuthToken()),
  });
  return next(clonedRequest);
}
```

The [`TestBed`](/api/core/testing/TestBed) configuration for this interceptor should rely on the [`withInterceptors`](/api/common/http/withInterceptors) feature.

```
TestBed.configureTestingModule({
  providers: [
    AuthService,
    // Testing one interceptor at a time is recommended.
    provideHttpClient(withInterceptors([authInterceptor])),
    provideHttpClientTesting(),
  ],
});
```

The [`HttpTestingController`](/api/common/http/testing/HttpTestingController) can retrieve the request instance that can then be inspected to ensure that the request was modified.

```
const service = TestBed.inject(AuthService);
const http = TestBed.inject(HttpClient);

// Make an HTTP request.
const response = firstValueFrom(http.get('/api/config'));

const req = httpTesting.expectOne('/api/config');

expect(req.request.headers.get('X-Authentication-Token')).toEqual(service.getAuthToken());
```

A similar interceptor could be implemented with class-based interceptors:

```
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private authService = inject(AuthService);

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const clonedRequest = request.clone({
      headers: request.headers.append('X-Authentication-Token', this.authService.getAuthToken()),
    });
    return next.handle(clonedRequest);
  }
}
```

In order to test it, the [`TestBed`](/api/core/testing/TestBed) configuration should instead be:

```
TestBed.configureTestingModule({
  providers: [
    AuthService,
    provideHttpClient(withInterceptorsFromDi()),
    provideHttpClientTesting(),
    // We rely on the HTTP_INTERCEPTORS token to register the AuthInterceptor as an HttpInterceptor
    {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
  ],
});
```

----
url: https://angular.dev/api/core/RendererType2
----

```
    
      interface RendererType2 {}
    
    
```

A unique identifying string for the new renderer, used when creating unique styles for encapsulation.

The view encapsulation type, which determines how styles are applied to DOM elements. One of

* `Emulated` (default): Emulate native scoping of styles.
* `Native`: Use the native encapsulation mechanism of the renderer.
* `ShadowDom`: Use modern [Shadow DOM](https://w3c.github.io/webcomponents/spec/shadow/) and create a ShadowRoot for component's host element.
* `None`: Do not provide any template or style encapsulation.

Defines CSS styles to be stored on a renderer instance.

Defines arbitrary developer-defined data to be stored on a renderer instance. This is useful for renderers that delegate to other renderers.

A function used by the framework to create the list of external runtime style URLs.

Jump to details

----
url: https://angular.dev/api/forms/signals/FORM_FIELD
----

```
    
      const FORM_FIELD: InjectionToken<FormField<unknown>>;
    
    
```

Jump to details

----
url: https://angular.dev/guide/directives/structural-directives
----

Structural directives are directives applied to an `<ng-template>` element that conditionally or repeatedly render the content of that `<ng-template>`.

For everyday conditional and repeated rendering, use Angular's built-in [control flow blocks](guide/templates/control-flow) (`@if`, `@for`, and `@switch`). Write a structural directive when you need reusable rendering behavior that control flow doesn't cover, such as gating content behind a permission check or providing a template with data from an external source.

## [Example use case](#example-use-case)

This guide uses a directive called `SelectDirective` as its running example. The directive fetches data from a given data source and renders its template when that data is available. It is named after the SQL keyword `SELECT` and matched with an attribute selector, `[select]`.

`SelectDirective` has an input naming the data source to use, called `selectFrom`. The `select` prefix for this input is important for the [shorthand syntax](#structural-directive-shorthand). The directive instantiates its `<ng-template>` with a template context providing the selected data.

Using this directive directly on an `<ng-template>` looks like this:

```
<ng-template select let-data [selectFrom]="source">
  <p>The data is: {{ data }}</p>
</ng-template>
```

The structural directive can wait for the data to become available and then render its `<ng-template>`.

**HELPFUL:** Angular's `<ng-template>` element defines a template that doesn't render anything by default. If you wrap elements in an `<ng-template>` without applying a structural directive, those elements are not rendered.

For more information, see the [ng-template API](api/core/ng-template) documentation.

## [Structural directive shorthand](#structural-directive-shorthand)

Angular supports a shorthand syntax for structural directives which avoids the need to explicitly author an `<ng-template>` element.

Structural directives can be applied directly on an element by prefixing the directive attribute selector with an asterisk (`*`), such as `*select`. Angular transforms the asterisk in front of a structural directive into an `<ng-template>` that hosts the directive and surrounds the element and its descendants.

You can use this with `SelectDirective` as follows:

```
<p *select="let data; from: source">The data is: {{ data }}</p>
```

This example shows the flexibility of structural directive shorthand syntax, which is sometimes called *microsyntax*.

When used in this way, only the structural directive and its bindings are applied to the `<ng-template>`. Any other attributes or bindings on the `<p>` tag are left alone. For example, these two forms are equivalent:

```
<!-- Shorthand syntax: -->
<p class="data-view" *select="let data; from: source">The data is: {{ data }}</p>

<!-- Long-form syntax: -->
<ng-template select let-data [selectFrom]="source">
  <p class="data-view">The data is: {{ data }}</p>
</ng-template>
```

Shorthand syntax is expanded through a set of conventions. A more thorough [grammar](#structural-directive-syntax-reference) is defined below, but in the above example, this transformation can be explained as follows:

The first part of the `*select` expression is `let data`, which declares a template variable `data`. Since no assignment follows, the template variable is bound to the template context property `$implicit`.

The second piece of syntax is a key-expression pair, `from source`. `from` is a binding key and `source` is a regular template expression. Binding keys are mapped to properties by transforming them to PascalCase and prepending the structural directive selector. The `from` key is mapped to `selectFrom`, which is then bound to the expression `source`. This is why many structural directives will have inputs that are all prefixed with the structural directive's selector.

## [One structural directive per element](#one-structural-directive-per-element)

You can only apply one structural directive per element when using the shorthand syntax. This is because there is only one `<ng-template>` element onto which that directive gets unwrapped. Multiple directives would require multiple nested `<ng-template>`, and it's unclear which directive should be first. `<ng-container>` can be used to create wrapper layers when multiple structural directives need to be applied around the same physical DOM element or component, which allows the user to define the nested structure.

## [Creating a structural directive](#creating-a-structural-directive)

A structural directive is a directive class that injects two dependencies:

* [`TemplateRef`](api/core/TemplateRef) gives the directive access to the content of the `<ng-template>` it is applied to.
* [`ViewContainerRef`](api/core/ViewContainerRef) represents the location in the DOM where the directive can render that template.

The directive controls rendering by creating, or not creating, embedded views from the template in the view container. The complete `SelectDirective` looks like this:

```
import {Directive, TemplateRef, ViewContainerRef, inject, input} from '@angular/core';

export interface DataSource<T> {
  load(): Promise<T>;
}

@Directive({
  selector: '[select]',
})
export class SelectDirective {
  private templateRef = inject(TemplateRef);
  private viewContainerRef = inject(ViewContainerRef);

  selectFrom = input.required<DataSource<unknown>>();

  async ngOnInit() {
    const data = await this.selectFrom().load();
    this.viewContainerRef.createEmbeddedView(this.templateRef, {
      // Create the embedded view with a context object that contains
      // the data via the key `$implicit`.
      $implicit: data,
    });
  }
}
```

The `selectFrom` input names the data source the directive reads from. It uses [`input.required()`](guide/components/inputs#required-inputs) because the directive can't do anything useful without a data source.

When Angular initializes the directive, it loads the data and then renders the template by calling `createEmbeddedView()`. The second argument is the template's *context object*: values the template can bind to with `let` declarations. Assigning the data to the `$implicit` key makes it the default value that `let-data` (or `let data` in shorthand) receives.

**NOTE:** This example renders its template once, when the directive initializes. It does not re-render when the bound data source changes.

Once the directive works, consider [adding template type-checking support](#typing-the-directives-context).

**HELPFUL:** The CLI command [`ng generate directive`](tools/cli/schematics) scaffolds a directive along with its test file.

## [Structural directive syntax reference](#structural-directive-syntax-reference)

When you write your own structural directives, use the following syntax:

```
_: prefix = "( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )_";
```

The following patterns describe each portion of the structural directive grammar:

```
as = :export "as" :local ";"?
keyExp = :key ":"? :expression ("as" :local)? ";"?
let = "let" :local "=" :export ";"?
```

| Keyword      | Details                                            |
| :----------- | :------------------------------------------------- |
| `prefix`     | HTML attribute key                                 |
| `key`        | HTML attribute key                                 |
| `local`      | Local variable name used in the template           |
| `export`     | Value exported by the directive under a given name |
| `expression` | Standard Angular expression                        |

### [How Angular translates shorthand](#how-angular-translates-shorthand)

Angular translates structural directive shorthand into the normal binding syntax as follows:

| Shorthand                       | Translation                                                     |
| :------------------------------ | :-------------------------------------------------------------- |
| `prefix` and naked `expression` | `[prefix]="expression"`                                         |
| `keyExp`                        | `[prefixKey]="expression"` (The `prefix` is added to the `key`) |
| `let local`                     | `let-local="export"`                                            |

### [Shorthand examples](#shorthand-examples)

The following table provides shorthand examples:

| Shorthand                                                             | How Angular interprets the syntax                                                                             |
| :-------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
| `*myDir="let item of [1,2,3]"`                                        | `<ng-template myDir let-item [myDirOf]="[1, 2, 3]">`                                                          |
| `*myDir="let item of [1,2,3] as items; trackBy: myTrack; index as i"` | `<ng-template myDir let-item [myDirOf]="[1,2,3]" let-items="myDirOf" [myDirTrackBy]="myTrack" let-i="index">` |
| `*ngComponentOutlet="componentClass"`                                 | `<ng-template [ngComponentOutlet]="componentClass">`                                                          |
| `*ngComponentOutlet="componentClass; inputs: myInputs"`               | `<ng-template [ngComponentOutlet]="componentClass" [ngComponentOutletInputs]="myInputs">`                     |
| `*myDir="exp as value"`                                               | `<ng-template [myDir]="exp" let-value="myDir">`                                                               |

## [Improving template type checking for custom directives](#improving-template-type-checking-for-custom-directives)

You can improve template type checking for custom directives by adding template guards to your directive definition. These guards help the Angular template type checker find mistakes in the template at compile time, which can avoid runtime errors. Two different types of guards are possible:

* `ngTemplateGuard_(input)` lets you control how an input expression should be narrowed based on the type of a specific input.
* `ngTemplateContextGuard` is used to determine the type of the context object for the template, based on the type of the directive itself.

This section provides examples of both kinds of guards. For more information, see [Template type checking](tools/cli/template-typecheck "Template type-checking guide").

### [Type narrowing with template guards](#type-narrowing-with-template-guards)

A structural directive in a template controls whether that template is rendered at run time. Some structural directives want to perform type narrowing based on the type of input expression.

There are two narrowings which are possible with input guards:

* Narrowing the input expression based on a TypeScript type assertion function.
* Narrowing the input expression based on its truthiness.

To narrow the input expression by defining a type assertion function:

```
// This directive only renders its template if the actor is a user.
// You want to assert that within the template, the type of the `actor`
// expression is narrowed to `User`.
@Directive(...)
class ActorIsUser {
  actor = input<User | Robot>();

  static ngTemplateGuard_actor(dir: ActorIsUser, expr: User | Robot): expr is User {
    // The return statement is unnecessary in practice, but included to
    // prevent TypeScript errors.
    return true;
  }
}
```

Type-checking will behave within the template as if the `ngTemplateGuard_actor` has been asserted on the expression bound to the input.

Some directives only render their templates when an input is truthy. It's not possible to capture the full semantics of truthiness in a type assertion function, so instead a literal type of `'binding'` can be used to signal to the template type-checker that the binding expression itself should be used as the guard:

```
@Directive(...)
class CustomIf {
  condition = input.required<boolean>();

  static ngTemplateGuard_condition: 'binding';
}
```

The template type-checker will behave as if the expression bound to `condition` was asserted to be truthy within the template.

### [Typing the directive's context](#typing-the-directives-context)

If your structural directive provides a context to the instantiated template, you can properly type it inside the template by providing a static `ngTemplateContextGuard` type assertion function. This function can use the type of the directive to derive the type of the context, which is useful when the type of the directive is generic.

For the `SelectDirective` described above, you can implement an `ngTemplateContextGuard` to correctly specify the data type, even if the data source is generic.

```
// Declare an interface for the template context:
export interface SelectTemplateContext<T> {
  $implicit: T;
}

@Directive(...)
export class SelectDirective<T> {
  // The directive's generic type `T` will be inferred from the `DataSource` type
  // passed to the input.
  selectFrom = input.required<DataSource<T>>();

  // Narrow the type of the context using the generic type of the directive.
  static ngTemplateContextGuard<T>(dir: SelectDirective<T>, ctx: any): ctx is SelectTemplateContext<T> {
    // As before the guard body is not used at runtime, and included only to avoid
    // TypeScript errors.
    return true;
  }
}
```

## [What's next](#whats-next)

----
url: https://angular.dev/api/common/BrowserPlatformLocation
----

```
    
      class BrowserPlatformLocation extends PlatformLocation {}
    
    
```

@returns`string`

@paramfn`LocationChangeListener`

@returns`VoidFunction`

@returns`unknown`

Jump to details

----
url: https://angular.dev/api/forms/signals/Schema
----

```
    
      type Schema<TModel> = {  [ɵɵTYPE]: SchemaFn<TModel, PathKind.Root>;}
    
    
```

## [Description](#description)

A reusable schema that defines behavior and rules for a form.

A [`Schema`](/api/forms/signals/Schema) encapsulates form logic such as validation rules, disabled states, readonly states, and other field-level behaviors.

Unlike raw [`SchemaFn`](/api/forms/signals/SchemaFn), a [`Schema`](/api/forms/signals/Schema) is created using the [`schema`](/api/forms/signals/schema) function and is cached per-form, even when applied to multiple fields.

### [Creating a reusable schema](#creating-a-reusable-schema)

```
interface Address {
  street: string;
  city: string;
}

// Create a reusable schema for address fields
const addressSchema = schema<Address>((p) => {
  required(p.street);
  required(p.city);
});

// Apply the schema to multiple forms
const shippingForm = form(shippingModel, addressSchema, {injector});
const billingForm = form(billingModel, addressSchema, {injector});
```

### [Passing a schema to a form](#passing-a-schema-to-a-form)

A schema can also be passed as a second argument to the [`form`](/api/forms/signals/form) function.

```
readonly userForm = form(addressModel, addressSchema);
```

Jump to details

----
url: https://angular.dev/api/core/Query
----

## [API](/api/core/Query#api)

```
    
      interface Query {}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpResourceOptions
----

## [API](/api/common/http/HttpResourceOptions#api)

```
    
      interface HttpResourceOptions<TResult, TRaw> {}
    
    
```

Transform the result of the HTTP request before it's delivered to the resource.

`parse` receives the value from the HTTP layer as its raw type (e.g. as `unknown` for JSON data). It can be used to validate or transform the type of the resource, and return a more specific type. This is also useful for validating backend responses using a runtime schema validation library such as Zod.

Value that the resource will take when in Idle or Loading states.

If not set, the resource will use `undefined` as its default value.

The [`Injector`](/api/core/Injector) in which to create the [`httpResource`](/api/common/http/httpResource).

If this is not provided, the current [injection context](guide/di/dependency-injection-context) will be used instead (via [`inject`](/api/core/inject)).

A comparison function which defines equality for the response value.

A debug name for the reactive node. Used in Angular DevTools to identify the node.

Jump to details

----
url: https://angular.dev/errors/NG0301
----

Angular can't find a directive with `{{ PLACEHOLDER }}` export name. The export name is specified in the `exportAs` property of the directive decorator. This is common when using FormsModule or Material modules in templates and you've forgotten to import the corresponding modules.

**HELPFUL:** This is the runtime equivalent of a common compiler error [NG8003: No directive found with export](errors/NG8003).

## [Debugging the error](#debugging-the-error)

Use the export name to trace the templates or modules using this export.

Ensure that all dependencies are properly imported and declared in your NgModules. For example, if the export not found is [`ngForm`](/api/forms/NgForm), we need to import [`FormsModule`](/api/forms/FormsModule) and declare it in the list of imports in `*.module.ts` to resolve the error.

```
import { FormsModule } from '@angular/forms';

@NgModule({
…
imports: [
FormsModule,
…
```

If you recently added an import, you may need to restart your server to see these changes.

----
url: https://angular.dev/api/core/OutputEmitterRef
----

```
    
      class OutputEmitterRef<T> implements OutputRef<T> {}
    
    
```

@paramcallback`(value: T) => void`

@returns`OutputRefSubscription`

Emits a new value to the output.

@paramvalue`T`

@returns`void`

## [Description](#description)

An [`OutputEmitterRef`](/api/core/OutputEmitterRef) is created by the [`output()`](/api/core/output) function and can be used to emit values to consumers of your directive or component.

Consumers of your directive/component can bind to the output and subscribe to changes via the bound event syntax. For example:

```
<my-comp (valueChange)="processNewValue($event)" />
```

Jump to details

----
url: https://angular.dev/api/router/ChildrenOutletContexts
----

## [API](/api/router/ChildrenOutletContexts#api)

```
    
      class ChildrenOutletContexts {}
    
    
```

Called when a [`RouterOutlet`](/api/router/RouterOutlet) directive is instantiated

@paramchildName`string`

@paramoutlet`RouterOutletContract`

@returns`void`

Called when a [`RouterOutlet`](/api/router/RouterOutlet) directive is destroyed. We need to keep the context as the outlet could be destroyed inside a NgIf and might be re-created later.

@paramchildName`string`

@returns`void`

Called when the corresponding route is deactivated during navigation. Because the component get destroyed, all children outlet are destroyed.

@returns`Map<string, OutletContext>`

@paramcontexts`Map<string, OutletContext>`

@returns`void`

@paramchildName`string`

@returns`OutletContext`

@paramchildName`string`

@returns`OutletContext | null`

Jump to details

----
url: https://angular.dev/tools/cli/serve
----

You can serve your Angular CLI application with the `ng serve` command. This will compile your application, skip unnecessary optimizations, start a devserver, and automatically rebuild and live reload any subsequent changes. You can stop the server by pressing `Ctrl+C`.

`ng serve` only executes the builder for the `serve` target in the default project as specified in `angular.json`. While any builder can be used here, the most common (and default) builder is `@angular/build:dev-server`.

You can determine which builder is being used for a particular project by looking up the `serve` target for that project.

```
{
  "projects": {
    "my-app": {
      "architect": {
        // `ng serve` invokes the Architect target named `serve`.
        "serve": {
          "builder": "@angular/build:dev-server"
          // ...
        },
        "build": {/* ... */},
        "test": {/* ... */}
      }
    }
  }
}
```

## [Proxying to a backend server](#proxying-to-a-backend-server)

Use [proxying support](https://vite.dev/config/server-options#server-proxy) to divert certain URLs to a backend server, by passing a file to the `--proxy-config` build option. For example, to divert all calls for `http://localhost:4200/api` to a server running on `http://localhost:3000/api`, take the following steps.

1. Create a file `proxy.conf.json` in your project's `src/` folder.
2. Add the following content to the new proxy file:

```
{
  "/api/**": {
    "target": "http://localhost:3000",
    "secure": false
  }
}
```

1. In the CLI configuration file, `angular.json`, add the `proxyConfig` option to the `serve` target:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "serve": {
          "builder": "@angular/build:dev-server",
          "options": {
            "proxyConfig": "src/proxy.conf.json"
          }
        }
      }
    }
  }
}
```

1. To run the development server with this proxy configuration, call `ng serve`.

**NOTE:** To apply changes made to your proxy configuration file, you must restart the `ng serve` process.

### [Path matching behavior](#path-matching-behavior)

* `/api` matches `/api` and any path that starts with it, such as `/api/users/123` or `/api-docs`.
* `/api/*` matches `/api/users`, but not `/api` or `/api/users/123`.
* `/api/**` matches `/api`, `/api/users`, and `/api/users/123`, but not `/api-docs`.

----
url: https://angular.dev/tutorials/signal-forms/3-add-validation
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/2-connect-form-template)[](/tutorials/signal-forms/4-display-errors)

[](/tutorials/signal-forms/2-connect-form-template)[](/tutorials/signal-forms/4-display-errors)

Adding validation to your form is critical to ensure users enter valid data. Signal Forms uses validators in a schema function that you pass to the [`form()`](/api/forms/signals/form) function.

In this activity, you'll learn how to:

* Import built-in validators
* Define a schema function for your form
* Apply validators to specific fields with custom error messages

Let's add validation!

***

1. ### [Import the validators](#import-the-validators)

   Import the `required` and `email` validators from `@angular/forms/signals`:

   ```
   import {form, FormField, required, email} from '@angular/forms/signals';
   ```

2. ### [Add a schema function to your form](#add-a-schema-function-to-your-form)

   Update your [`form()`](/api/forms/signals/form) call to include a schema function as the second parameter. The schema function receives a `fieldPath` parameter that lets you access each field:

   ```
   loginForm = form(this.loginModel, (fieldPath) => {
     // Validators will go here
   });
   ```

3. ### [Add validation to the email field](#add-validation-to-the-email-field)

   Inside the schema function, add validation for the email field. Use both `required()` and `email()` validators:

   ```
   loginForm = form(this.loginModel, (fieldPath) => {
     required(fieldPath.email, {message: 'Email is required'});
     email(fieldPath.email, {message: 'Enter a valid email address'});
   });
   ```

   The `message` option provides custom error messages for users.

4. ### [Add validation to the password field](#add-validation-to-the-password-field)

   Add validation for the password field using the `required()` validator:

   ```
   loginForm = form(this.loginModel, (fieldPath) => {
     required(fieldPath.email, {message: 'Email is required'});
     email(fieldPath.email, {message: 'Enter a valid email address'});
     required(fieldPath.password, {message: 'Password is required'});
   });
   ```

Perfect! You've added validation to your form. The validators run automatically as users interact with the form. When validation fails, the field's state will reflect the errors.

Next, you'll learn [how to display validation errors in the template](/tutorials/signal-forms/4-display-errors)!

----
url: https://angular.dev/api/common/getLocaleCurrencySymbol
----

Use the `Intl` API to format a currency with from currency code

## [API](#api)

```
function getLocaleCurrencySymbol(locale: string): string | null;
```

@deprecated

Use the `Intl` API to format a currency with from currency code

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`string | null`

The localized symbol character, or `null` if the main country cannot be determined.

Jump to details

----
url: https://angular.dev/api/common/http/HttpResourceFn
----

## [API](/api/common/http/HttpResourceFn#api)

```
    
      interface HttpResourceFn {
  <TResult = unknown>(url: (ctx: ResourceParamsContext) => string | undefined, options: HttpResourceOptions<TResult, unknown> & { defaultValue: NoInfer<TResult>; }): HttpResourceRef<TResult>;  <TResult = unknown>(url: (ctx: ResourceParamsContext) => string | undefined, options?: HttpResourceOptions<TResult, unknown> | undefined): HttpResourceRef<TResult | undefined>;  <TResult = unknown>(request: (ctx: ResourceParamsContext) => HttpResourceRequest | undefined, options: HttpResourceOptions<TResult, unknown> & { defaultValue: NoInfer<TResult>; }): HttpResourceRef<TResult>;  <TResult = unknown>(request: (ctx: ResourceParamsContext) => HttpResourceRequest | undefined, options?: HttpResourceOptions<TResult, unknown> | undefined): HttpResourceRef<TResult | undefined>;
}
    
    
```

Create a `Resource` that fetches data with an HTTP GET request to the given URL.

The resource will update when the URL changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed as JSON by default - use a sub-function of [`httpResource`](/api/common/http/httpResource), such as [`httpResource.text()`](/api/common/http/httpResource#text), to parse the response differently.

@paramurl`(ctx: ResourceParamsContext) => string | undefined`

@paramoptions`HttpResourceOptions<TResult, unknown> & { defaultValue: NoInfer<TResult>; }`

@returns`HttpResourceRef<TResult>`

Create a `Resource` that fetches data with an HTTP GET request to the given URL.

The resource will update when the URL changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed as JSON by default - use a sub-function of [`httpResource`](/api/common/http/httpResource), such as [`httpResource.text()`](/api/common/http/httpResource#text), to parse the response differently.

@paramurl`(ctx: ResourceParamsContext) => string | undefined`

@paramoptions`HttpResourceOptions<TResult, unknown> | undefined`

@returns`HttpResourceRef<TResult | undefined>`

Create a `Resource` that fetches data with the configured HTTP request.

The resource will update when the request changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed as JSON by default - use a sub-function of [`httpResource`](/api/common/http/httpResource), such as [`httpResource.text()`](/api/common/http/httpResource#text), to parse the response differently.

@paramrequest`(ctx: ResourceParamsContext) => HttpResourceRequest | undefined`

@paramoptions`HttpResourceOptions<TResult, unknown> & { defaultValue: NoInfer<TResult>; }`

@returns`HttpResourceRef<TResult>`

Create a `Resource` that fetches data with the configured HTTP request.

The resource will update when the request changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed as JSON by default - use a sub-function of [`httpResource`](/api/common/http/httpResource), such as [`httpResource.text()`](/api/common/http/httpResource#text), to parse the response differently.

@paramrequest`(ctx: ResourceParamsContext) => HttpResourceRequest | undefined`

@paramoptions`HttpResourceOptions<TResult, unknown> | undefined`

@returns`HttpResourceRef<TResult | undefined>`

Create a `Resource` that fetches data with the configured HTTP request.

The resource will update when the URL or request changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed into an `ArrayBuffer`.

Create a `Resource` that fetches data with the configured HTTP request.

The resource will update when the URL or request changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed into a `Blob`.

Create a `Resource` that fetches data with the configured HTTP request.

The resource will update when the URL or request changes via signals.

Uses [`HttpClient`](/api/common/http/HttpClient) to make requests and supports interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API. Data is parsed as a `string`.

Jump to details

----
url: https://angular.dev/api/common/http/testing/TestRequest
----

```
    
      class TestRequest {
}
    
    
```

@paramrequest`HttpRequest<any>`

@paramobserver`Observer<HttpEvent<any>>`

@returns`TestRequest`

Whether the request was cancelled after it was sent.

Resolve the request by returning a body plus additional HTTP information (such as response headers) if provided. If the request specifies an expected body type, the body is converted into the requested type. Otherwise, the body is converted to `JSON` by default.

Both successful and unsuccessful responses can be delivered via `flush()`.

@parambody`string | number | boolean | Object | ArrayBuffer | Blob | (string | number | boolean | Object | null)[] | null`

@paramopts`{ headers?: HttpHeaders | { [name: string]: string | string[]; } | undefined; status?: number | undefined; statusText?: string | undefined; }`

@returns`void`

Resolve the request by returning an `ErrorEvent` (e.g. simulating a network failure).

@deprecated

Http requests never emit an `ErrorEvent`. Please specify a `ProgressEvent`.

@paramerror`ErrorEvent`

@paramopts`TestRequestErrorOptions | undefined`

@returns`void`

Resolve the request by returning an `ProgressEvent` (e.g. simulating a network failure).

@paramerror`ProgressEvent<EventTarget>`

@paramopts`TestRequestErrorOptions | undefined`

@returns`void`

Deliver an arbitrary `HttpEvent` (such as a progress event) on the response stream for this request.

@paramevent`HttpEvent<any>`

@returns`void`

## [Description](#description)

A mock requests that was received and is ready to be answered.

This interface allows access to the underlying `HttpRequest`, and allows responding with `HttpEvent`s or `HttpErrorResponse`s.

Jump to details

----
url: https://angular.dev/api/core/provideCheckNoChangesConfig
----

```
function provideCheckNoChangesConfig(options: {
  exhaustive: false;
}): EnvironmentProviders;
function provideCheckNoChangesConfig(options: {
  interval?: number | undefined;
  exhaustive: true;
}): EnvironmentProviders;
```

Used to disable exhaustive checks when verifying no expressions changed after they were checked.

This means that `OnPush` components that are not marked for check will not be checked. This behavior is the current default behavior in Angular. When running change detection on a view tree, views marked for check are refreshed and the flag to check it is removed. When Angular checks views a second time to ensure nothing has changed, `OnPush` components will no longer be marked and not be checked.

@paramoptions`{ exhaustive: false; }`

@returns`EnvironmentProviders`

* `interval` will periodically run `checkNoChanges` on application views. This can be useful in zoneless applications to periodically ensure no changes have been made without notifying Angular that templates need to be refreshed.
* The exhaustive option will treat all application views as if they were [`ChangeDetectionStrategy.Eager`](/api/core/ChangeDetectionStrategy#Eager)/`Default` when verifying no expressions have changed. All views attached to [`ApplicationRef`](/api/core/ApplicationRef) and all the descendants of those views will be checked for changes (excluding those subtrees which are detached via [`ChangeDetectorRef.detach()`](/api/core/ChangeDetectorRef#detach)). This is useful because the check that runs after regular change detection does not work for components using [`ChangeDetectionStrategy.OnPush`](/api/core/ChangeDetectionStrategy#OnPush). This check is will surface any existing errors hidden by `OnPush` components.

@paramoptions`{ interval?: number | undefined; exhaustive: true; }`

@returns`EnvironmentProviders`

## [Description](#description)

Used to disable exhaustive checks when verifying no expressions changed after they were checked.

This means that `OnPush` components that are not marked for check will not be checked. This behavior is the current default behavior in Angular. When running change detection on a view tree, views marked for check are refreshed and the flag to check it is removed. When Angular checks views a second time to ensure nothing has changed, `OnPush` components will no longer be marked and not be checked.

Jump to details

----
url: https://angular.dev/api/core/testing/MetadataOverride
----

```
    
      type MetadataOverride<T> = {  add?: Partial<T>;  remove?: Partial<T>;  set?: Partial<T>;}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/provideZonelessChangeDetection
----

```
function provideZonelessChangeDetection(): EnvironmentProviders;
```

@returns`EnvironmentProviders`

## [Description](#description)

Provides change detection without ZoneJS for the application bootstrapped using `bootstrapApplication`.

**NOTE:** Zoneless is enabled by default in Angular v21+. Ensure [`provideZoneChangeDetection`](/api/core/provideZoneChangeDetection) is not used to override this default.

This function allows you to configure the application to not use the state/state changes of ZoneJS to schedule change detection in the application. This will work when ZoneJS is not present on the page at all or if it exists because something else is using it (either another Angular application which uses ZoneJS for scheduling or some other library that relies on ZoneJS).

This can also be added to the `TestBed` providers to configure the test environment to more closely match production behavior. This will help give higher confidence that components are compatible with zoneless change detection.

ZoneJS uses browser events to trigger change detection. When using this provider, Angular will instead use Angular APIs to schedule change detection. These APIs include:

* [`ChangeDetectorRef.markForCheck`](/api/core/ChangeDetectorRef#markForCheck)
* [`ComponentRef.setInput`](/api/core/ComponentRef#setInput)
* updating a signal that is read in a template
* when bound host or template listeners are triggered
* attaching a view that was marked dirty by one of the above
* removing a view
* registering a render hook (templates are only refreshed if render hooks do one of the above)

## [Usage Notes](#usage-notes)

```
bootstrapApplication(MyApp, {providers: [
  provideZonelessChangeDetection(),
]});
```

Jump to details

----
url: https://angular.dev/api/forms/signals/applyWhen
----

```
function applyWhen<TValue>(
  path: SchemaPath<TValue>,
  logic: LogicFn<TValue, boolean>,
  schema: NoInfer<SchemaOrSchemaFn<TValue>>,
): void;
```

@parampath`SchemaPath<TValue>`

The target path to apply the schema to.

@paramlogic`LogicFn<TValue, boolean>`

A `LogicFn<T, boolean>` that returns `true` when the schema should be applied.

@paramschema`NoInfer<SchemaOrSchemaFn<TValue>>`

The schema to apply to the field when the `logic` function returns `true`.

@returns`void`

Jump to details

----
url: https://angular.dev/errors/NG0318
----

Angular detected a value with an unsupported type in a style property binding. This can occur in an individual binding such as `[style.width]` or in a property of a `[style]` map.

Style property values accept strings, numbers, style [`SafeValue`](/api/platform-browser/SafeValue) objects, `null`, and `undefined`. Other values, including [`SafeValue`](/api/platform-browser/SafeValue) objects created for a non-style security context, produce this warning in development mode.

For example, the following binding passes a boolean instead of a CSS value:

```
<div [style.display]="isVisible"></div>
```

## [Debugging the error](#debugging-the-error)

Use the property name and value in the error message to locate the binding. Convert the value to a valid CSS string or number, or use `null` or `undefined` to remove the style.

When using a unit suffix such as `[style.width.px]`, bind the numeric portion of the value:

```
<div [style.width.px]="width"></div>
```

Do not pass a [`SafeValue`](/api/platform-browser/SafeValue) created for another security context, such as trusted HTML, to a style property binding.

----
url: https://angular.dev/errors/NG8001
----

One or more elements cannot be resolved during compilation because the element is not defined by the HTML spec, or there is no component or directive with such element selector.

**HELPFUL:** This is the compiler equivalent of a common runtime error `NG0304: '${tagName}' is not a known element: ...`.

## [Debugging the error](/errors/NG8001#debugging-the-error)

Use the element name in the error to find the file(s) where the element is being used.

Check that the name and selector are correct.

Make sure that the component is correctly imported inside your NgModule or standalone component, by checking its presence in the `imports` field. If the component is declared in an NgModule (meaning that it is not standalone) make sure that it is exported correctly from it, by checking its presence in the `exports` field.

When using custom elements or web components, ensure that you add [`CUSTOM_ELEMENTS_SCHEMA`](api/core/CUSTOM_ELEMENTS_SCHEMA) to the application module.

If this does not resolve the error, check the imported libraries for any recent changes to the exports and properties you are using, and restart your server.

----
url: https://angular.dev/guide/forms/signals/model-design
----

Signal Forms uses a model-driven approach, deriving the form's state and structure directly from the model you provide. Because it serves as the foundation of the entire form, it is important to start with a well-designed form model. This guide explores best practices for designing form models.

## [Form model vs domain model](#form-model-vs-domain-model)

Forms are used to collect user input. Your application likely has a domain model used to represent this input in a way that's optimized for business logic or storage. However, this is often *different* than how we want to model the data in our form.

The form model represents the raw user input as it appears in the UI. For instance, in a form you might ask a user to pick a date and a time slot for an appointment as separate input fields, even if your domain model represents it as a single JavaScript `Date` object.

```
interface AppointmentFormModel {
  name: string; // Appointment owner's name
  date: Date; // Appointment date (carries only date information, time component is unused)
  time: string; // Selected time as a string
}

interface AppointmentDomainModel {
  name: string; // Appointment owner's name
  time: Date; // Appointment time (carries both date and time information)
}
```

Forms should use a form model tailored to the input experience, rather than simply repurposing the domain model.

## [Form model best practices](#form-model-best-practices)

### [Use specific types](#use-specific-types)

Always define interfaces or types for your models as shown in [Using TypeScript types](/guide/forms/signals/models#using-typescript-types). Explicit types provide better IntelliSense, catch errors at compile time, and serve as documentation for what data the form contains.

### [Initialize all fields](#initialize-all-fields)

Provide initial values for every field in your model:

Prefer

### All fields initialized

```
const taskModel = signal({
  title: '',
  description: '',
  priority: 'medium',
  completed: false,
});
```

Avoid

### Partial initialization

```
const taskModel = signal({
  title: '',
  // Missing description, priority, completed
});
```

Missing initial values mean those fields won't exist in the field tree, making them inaccessible for form interactions.

### [Keep models focused](#keep-models-focused)

Each model should represent a single form or a cohesive set of related data:

Prefer

### Focused on a single purpose

```
const loginModel = signal({
  email: '',
  password: '',
});
```

Avoid

### Mixing unrelated concerns

```
const appModel = signal({
  // Login data
  email: '',
  password: '',
  // User preferences
  theme: 'light',
  language: 'en',
  // Shopping cart
  cartItems: [],
});
```

Separate models for different concerns makes forms easier to understand and reuse. Create multiple forms if you're managing distinct sets of data.

### [Consider validation requirements](#consider-validation-requirements)

Design models with validation in mind. Group fields that validate together:

Prefer

### Related fields grouped for comparison

```
// Password fields grouped for comparison
interface PasswordChangeData {
  currentPassword: string;
  newPassword: string;
  confirmPassword: string;
}
```

This structure makes cross-field validation (like checking if `newPassword` matches `confirmPassword`) more natural.

### [Match data types to UI controls](#match-data-types-to-ui-controls)

Properties on your form model should match the data types expected by your UI controls.

For example, consider a beverage order form with a `size` field (6, 12, or 24 pack) and a `quantity` field. The UI uses a dropdown (`<select>`) for size and a number input (`<input type="number">`) for quantity.

Although the size options look numeric, `<select>` elements work with string values, so `size` should be modeled as a string. An `<input type="number">` on the other hand, does work with numbers, so `quantity` can be modeled as a number.

Prefer

### Appropriate data types for the bound UI controls

```
interface BeverageOrderFormModel {
  size: string; // Bound to: <select> (option values: "6", "12", "24")
  quantity: number; // Bound to: <input type="number">
}
```

### [Avoid `undefined`](#avoid-undefined)

A form model must not contain `undefined` values or properties. In Signal Forms the structure of the form is derived from the structure of the model, and `undefined` signifies the *absence of a field*, rather than a field with an empty value. This means you must also avoid optional fields (e.g., `{property?: string}`), as they implicitly allow `undefined`.

To represent a property with an empty value in your form model, use a value that the UI control understands to mean "empty" (e.g. `""` for a `<input type="text">`). If you're designing a custom UI control, `null` often works as a good value to signify "empty".

Prefer

### Appropriate empty values

```
interface UserFormModel {
  name: string; // Bound to <input type="text">
  birthday: Date | null; // Bound to <input type="date">
}

// Initialize our form with empty values.
form(signal({name: '', birthday: null}));
```

### [Avoid models with dynamic structure](#avoid-models-with-dynamic-structure)

A form model has a dynamic structure if it changes shape (if the properties on the object change) based on its value. This happens when the model type allows for values with different shapes, such as a union of object types that have different properties, or a union of an object and a primitive. The following sections examine a few common scenarios where models with a dynamic structure might seem appealing, but ultimately prove problematic.

#### [Empty value for a complex object](#empty-value-for-a-complex-object)

We often use forms to ask users to enter brand new data, rather than edit existing data in a system. A good example of this is an account creation form. We might model that using the following form model.

```
interface CreateAccountFormModel {
  name: {
    first: string;
    last: string;
  };
  username: string;
}
```

When creating the form we encounter a dilemma, what should the initial value in the model be? It may be tempting to create a `form<CreateAccountFormModel | null>()` since we don't have any input from the user yet.

Avoid

### Using null as empty value for complex object

```
createAccountForm = form<CreateAccountFormModel | null>(signal(/* what goes here, null? */));
```

However, it is important to remember that Signal Forms is *model driven*. If our model is `null` and `null` doesn't have a `name` or `username` property, that means our form won't have those subfields either. Instead what we really want is an instance of `CreateAccountFormModel` with all of its leaf fields set to an empty value.

Prefer

### Same shape value with empty values for properties

```
createAccountForm = form<CreateAccountFormModel>(
  signal({
    name: {
      first: '',
      last: '',
    },
    username: '',
  }),
);
```

Using this representation, all of the subfields we need now exist, and we can bind them using the `[formField]` directive in our template.

```
First: <input [formField]="createAccountForm.name.first" /> Last:
<input [formField]="createAccountForm.name.last" /> Username:
<input [formField]="createAccountForm.username" />
```

#### [Fields that are conditionally hidden or unavailable](#fields-that-are-conditionally-hidden-or-unavailable)

Forms aren't always linear. You often need to create conditional paths based on previous user input. One example of this is a form where we give the user different payment options. Let's start by imagining what the UI for such a form might look like.

```
Name: <input type="text" />

<section>
  <h2>Payment Info</h2>
  <input type="radio" /> Credit Card @if (/* credit card selected */) {
  <section>
    Card Number <input type="text" /> Security Code <input type="text" /> Expiration
    <input type="text" />
  </section>
  }
  <input type="radio" /> Bank Account @if (/* bank account selected */) {
  <section>Account Number <input type="text" /> Routing Number <input type="text" /></section>
  }
</section>
```

The best way to handle this is to use a form model with a static structure that includes fields for *all* potential payment methods. In our schema, we can hide or disable the fields that are not currently available.

Prefer

### Static structure model

```
interface BillPayFormModel {
  name: string;
  method: {
    type: string;
    card: {
      cardNumber: string;
      securityCode: string;
      expiration: string;
    };
    bank: {
      accountNumber: string;
      routingNumber: string;
    };
  };
}

const billPaySchema = schema<BillPayFormModel>((billPay) => {
  // Hide credit card details when user has selected a method other than credit card.
  hidden(billPay.method.card, {when: ({valueOf}) => valueOf(billPay.method.type) !== 'card'});
  // Hide bank account details when user has selected a method other than bank account.
  hidden(billPay.method.bank, {when: ({valueOf}) => valueOf(billPay.method.type) !== 'bank'});
});
```

Using this model, both `card` and `bank` objects are always present in the form's state. When the user switches payment methods, we only update the `type` property. The data they entered into the card fields remains safely stored in the `card` object, ready to be redisplayed if they switch back.

In contrast, a dynamic form model may initially seem like a good fit for this use case. After all, we don't need fields for account and routing number if the user selected "Credit Card". We may be tempted to model this as a discriminated union:

Avoid

### Dynamic structure model

```
interface BillPayFormModel {
  name: string;
  method:
    | {
        type: 'card';
        cardNumber: string;
        securityCode: string;
        expiration: string;
      }
    | {
        type: 'bank';
        accountNumber: string;
        routingNumber: string;
      };
}
```

However, consider what would happen in the following scenario:

1. User fills out their name and credit card information
2. They're about to submit, but at the last moment they notice the convenience fee.
3. They toggle to the bank account option instead, figuring they might as well avoid the fee.
4. As they're about to enter the bank account info, they have second thoughts, they wouldn't want it to wind up in a leak.
5. They toggle back to credit card option, but they notice all the info they just entered is gone!

This illustrates another problem with form models that have a dynamic structure: they can cause data loss. A model like this assumes that once a field becomes hidden, the information in it will never be needed again. It replaces the credit card information with the bank information, and has no way to get the credit card information back.

#### [Exceptions](#exceptions)

While static structure is generally preferred, there are specific scenarios where dynamic structure is necessary and supported.

##### [Arrays](#arrays)

Arrays are the most common exception. Forms often need to collect a variable number of items, such as a list of phone numbers, attendees, or line items in an order.

```
interface SendEmailFormModel {
  subject: string;
  recipientEmails: string[];
}
```

In this case, the `recipientEmails` array grows and shrinks as the user interacts with the form. While the length of the array is dynamic, the structure of the individual items should be consistent (each item should have the same shape).

##### [Fields that are treated atomically by the UI control](#fields-that-are-treated-atomically-by-the-ui-control)

Another case where dynamic structure is acceptable is when a complex object is treated as a single, atomic value by the UI control. That is, if the control does not attempt to bind to or access any of its sub-fields individually. In this scenario, the control updates the value by replacing the entire object at once, rather than modifying its internal properties. Because the form structure is irrelevant in this scenario, it's acceptable for that structure to be dynamic.

For example, consider a user profile form that includes a `location` field. The location is selected using a complex "location picker" widget (perhaps a map or a search-ahead dropdown) that returns a coordinate object. In the case where the location is not yet selected, or the user chooses not to share their location, the picker indicates the location as `null`.

Prefer

### Dynamic structure is ok when field is treated as atomic

```
interface Location {
  lat: number;
  lng: number;
}

interface UserProfileFormModel {
  username: string;
  // This property has dynamic structure,
  // but that's ok because the location picker treats this field as atomic.
  location: Location | null;
}
```

In the template, we bind the `location` field directly to our custom control:

```
Username: <input [formField]="userForm.username" /> Location:
<location-picker [formField]="userForm.location"></location-picker>
```

Here, `<location-picker>` consumes and produces the entire [`Location`](/api/common/Location) object (or `null`), and doesn't access `userForm.location.lat` or `userForm.location.lng`. Therefore, `location` can safely have a dynamic shape without violating the principles of model-driven forms.

## [Translating between form model and domain model](#translating-between-form-model-and-domain-model)

Given that the form model and domain model represent the same concept differently, we need to have a way to translate between these different representations. When we want to present some existing data in the system to the user in a form, we need to transform it from the domain model representation to the form model representation. Conversely when we want to save a user's changes, we need to transform the data from the form model representation to the domain model representation.

Let's imagine that we have a domain model and a form model and we've written some functions to convert between them.

```
interface MyDomainModel { ... }

interface MyFormModel { ... }

// Instance of `MyFormModel` populated with empty input (e.g. `''` for string inputs, etc.)
const EMPTY_MY_FORM_MODEL: MyFormModel = { ... };

function domainModelToFormModel(domainModel: MyDomainModel): MyFormModel { ... }

function formModelToDomainModel(formModel: MyFormModel): MyDomainModel { ... }
```

### [Domain model to form model](#domain-model-to-form-model)

When we're creating a form to edit some existing domain model in the system, we'll typically receive that domain model either as an [`input()`](/api/core/input) to our form component or from a backend (e.g. via a resource). In either case, [`linkedSignal`](/api/core/linkedSignal) provides an excellent way to apply our transform.

In the case where we receive the domain model as an [`input()`](/api/core/input), we can use [`linkedSignal`](/api/core/linkedSignal) to create a writable form model from the input signal.

Prefer

### Use linkedSignal to convert domain model to form model

```
@Component(...)
class MyForm {
  // The domain model to initialize the form with, if not given we start with an empty form.
  readonly domainModel = input<MyDomainModel>();

  private readonly formModel = linkedSignal({
    // Linked signal based on the domain model
    source: this.domainModel,
    // If domain model is defined convert it to a form model, otherwise use an empty form model.
    computation: (domainModel) => domainModel
      ? domainModelToFormModel(domainModel)
      : EMPTY_MY_FORM_MODEL
  });

  protected readonly myForm = form(this.formModel);
}
```

Similarly, when we receive the domain model from the backend via a resource, we can create a [`linkedSignal`](/api/core/linkedSignal) based on its value to create our `formModel`. In this scenario, the domain model may take some time to fetch, and we should disable the form until the data is loaded.

Prefer

### Disable or hide the form when data is unavailable

```
@Component(...)
class MyForm {
  // Fetch the domain model from the backend.
  readonly domainModelResource: ResourceRef<MyDomainModel | undefined> = httpResource(...);

  private readonly formModel = linkedSignal({
    // Linked signal based on the domain model resource
    source: this.domainModelResource.value,
    // Convert the domain model once it loads, use an empty form model while loading.
    computation: (domainModel) => domainModel
      ? domainModelToFormModel(domainModel)
      : EMPTY_MY_FORM_MODEL
  });

  protected readonly myForm = form(this.formModel, (root) => {
    // Disable the entire form when the resource is loading.
    disabled(root, {when: () => this.domainModelResource.isLoading()});
  });
}
```

The examples above show a pure derivation of the form model, directly from the domain model. However, in some cases you may wish to do a more advanced diff operation between the new domain model value and the previous domain model and form model values. This can be implemented based on the [`linkedSignal`](/api/core/linkedSignal) [previous state](/guide/signals/linked-signal#accounting-for-previous-state).

### [Form model to domain model](#form-model-to-domain-model)

When we're ready to save the user's input back to the system, we need to convert it to the domain model representation. This would typically happen when the user submits the form, or continuously as the user edits for an auto-saving form.

To save on submit, we can handle the conversion in the [`submit`](/api/forms/signals/submit) function.

Prefer

### Convert form model to domain model on submit

```
@Component(...)
class MyForm {
  private readonly myDataService = inject(MyDataService);

  protected readonly myForm = form<MyFormModel>(...);

  handleSubmit() {
    submit(this.myForm, async () => {
      await this.myDataService.update(formModelToDomainModel(this.myForm().value()));
    });
  };
}
```

Alternatively, you could also send the form model directly to the server and do the conversion from form model to domain model on the server.

For continuous saving, update the domain model in an [`effect`](/api/core/effect).

Prefer

### Convert form model to domain model in an effect for auto-saving

```
@Component(...)
class MyForm {
  readonly domainModel = model.required<MyDomainModel>()

  protected readonly myForm = form(...);

  constructor() {
    effect(() => {
      // When the form model changes to a valid value, update the domain model.
      if (this.myForm().valid()) {
        this.domainModel.set(formModelToDomainModel(this.myForm().value()));
      }
    });
  };
}
```

The examples above show a pure conversion from the form model to the domain model. However, it is perfectly acceptable to consider the full form state in addition to just the form model value. For example, to save bytes we might want to only send partial updates to the server based on what the user changed. In this case our conversion function could be designed to take the entire form state and return a sparse domain model based on the form's values and dirtiness.

```
type Sparse<T> = T extends object ? {
    [P in keyof T]?: Sparse<T[P]>;
} : T;

function formStateToPartialDomainModel(
  formState: FieldState<MyFormModel>
): Sparse<MyDomainModel> { ... }
```

----
url: https://angular.dev/guide/testing/component-harnesses-overview
----

A **component harness** is a class that allows tests to interact with components the way an end user does via a supported API. You can create test harnesses for any component, ranging from small reusable widgets to full pages.

Harnesses offer several benefits:

* They make tests less brittle by insulating themselves against implementation details of a component, such as its DOM structure
* They make tests become more readable and easier to maintain
* They can be used across multiple testing environments

```
// Example of test with a harness for a component called MyButtonComponent
it('should load button with exact text', async () => {
  const button = await loader.getHarness(MyButtonComponentHarness);
  expect(await button.getText()).toBe('Confirm');
});
```

Component harnesses are especially useful for shared UI widgets. Developers often write tests that depend on private implementation details of widgets, such as DOM structure and CSS classes. Those dependencies make tests brittle and hard to maintain. Harnesses offer an alternative— a supported API that interacts with the widget the same way an end-user does. Widget implementation changes now become less likely to break user tests. For example, [Angular Material](https://material.angular.dev/components/categories) provides a test harness for each component in the library.

Component harnesses support multiple testing environments. You can use the same harness implementation in both unit and end-to-end tests. Test authors only need to learn one API and component authors don't have to maintain separate unit and end-to-end test implementations.

Many developers can be categorized by one of the following developer type categories: test authors, component harness authors, and harness environment authors. Use the table below to find the most relevant section in this guide based on these categories:

| Developer Type              | Description                                                                                                                                                                                                                                                                                            | Relevant Section                                                                                             |
| :-------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------- |
| Test Authors                | Developers that use component harnesses written by someone else to test their application. For example, this could be an app developer who uses a third-party menu component and needs to interact with the menu in a unit test.                                                                       | [Using component harnesses in tests](guide/testing/using-component-harnesses)                                |
| Component harness authors   | Developers who maintain some reusable Angular components and want to create a test harness for its users to use in their tests. For example, an author of a third party Angular component library or a developer who maintains a set of common components for a large Angular application.             | [Creating component harnesses for your components](guide/testing/creating-component-harnesses)               |
| Harness environment authors | Developers who want to add support for using component harnesses in additional testing environments. For information on supported testing environments out-of-the-box, see the [test harness environments and loaders](guide/testing/using-component-harnesses#test-harness-environments-and-loaders). | [Adding support for additional testing environments](guide/testing/component-harnesses-testing-environments) |

For the full API reference, please see the [Angular CDK's component harness API reference page](/api#angular_cdk_testing).

----
url: https://angular.dev/tools/devtools/injectors
----

**NOTE:** The Injector Tree is available for Angular Applications built with version 17 or higher.

## [View the injector hierarchy of your application](#view-the-injector-hierarchy-of-your-application)

The **Injector Tree** tab lets you explore the structure of the Injectors configured in your application. Here you will see two trees representing the [injector hierarchy](guide/di/hierarchical-dependency-injection) of your application. One tree is your environment hierarchy, the other is your element hierarchy.

## [Visualize resolution paths](#visualize-resolution-paths)

When a specific injector is selected, the path that Angular's dependency injection algorithm traverses from that injector to the root is highlighted. For element injectors, this includes highlighting the environment injectors that the dependency injection algorithm jumps to when a dependency cannot be resolved in the element hierarchy.

See [resolution rules](guide/di/hierarchical-dependency-injection#resolution-rules) for more details about how Angular resolves resolution paths.

## [View injector providers](#view-injector-providers)

Clicking an injector that has configured providers will display those providers in a list on the right of the injector tree view. Here you can view the provided token and it's type. The button on the right of each provider allows you to log the provider in the console.

----
url: https://angular.dev/api/core/@let
----

`@let` allows you to define a local variable and re-use it across the template.

## [Syntax](#syntax)

```
@let name = user.name;
@let data = data$ | async;
```

## [Description](#description)

`@let` declarations are similar to [JavaScript's `let`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let) and their values can be any valid Angular expression. The expressions will be re-evaluated every time the template is executed.

Similarly to JavaScript variables, variables declared with `@let`, can be used:

* After they have been declared
* Within the bound of the same scope and nested scopes

```
@let user = user$ | async;

@if (user) {
  <h1>Hello, {{ user.name }}</h1>
  <user-avatar [photo]="user.photo" />

  <ul>
    @for (snack of user.favoriteSnacks; track snack.id) {
      <li>{{ snack.name }}</li>
    }
  </ul>

  <button (click)="update(user)">Update profile</button>
}
```

## [Syntax definition](#syntax-definition)

The `@let` syntax is formally defined as:

* The `@let` keyword.
* Followed by one or more whitespaces, not including new lines.
* Followed by a valid JavaScript name and zero or more whitespaces.
* Followed by the = symbol and zero or more whitespaces.
* Followed by an Angular expression which can be multi-line.
* Terminated by the `;` symbol.

**HELPFUL:** A comprehensive description of the feature is available on [the templates guide](guide/templates/variables#local-template-variables-with-let)

Jump to details

----
url: https://angular.dev/api/core/StaticClassProvider
----

```
    
      interface StaticClassProvider extends StaticClassSansProvider {}
    
    
```

An injection token. Typically an instance of [`Type`](/api/core/Type) or [`InjectionToken`](/api/core/InjectionToken), but can be `any`.

When true, injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

An optional class to instantiate for the `token`. By default, the `provide` class is instantiated.

A list of `token`s to be resolved by the injector. The list of values is then used as arguments to the `useClass` constructor.

## [Usage Notes](#usage-notes)

```
abstract class Shape {
          name!: string;
        }

        class Square extends Shape {
          override name = 'square';
        }

        const injector = Injector.create({
          providers: [{provide: Shape, useClass: Square, deps: []}],
        });

        const shape: Shape = injector.get(Shape);
        expect(shape.name).toEqual('square');
        expect(shape instanceof Square).toBe(true);
```

Note that following two providers are not equal:

```
class Greeting {
          salutation = 'Hello';
        }

        class FormalGreeting extends Greeting {
          override salutation = 'Greetings';
        }

        const injector = Injector.create({
          providers: [
            {provide: FormalGreeting, useClass: FormalGreeting, deps: []},
            {provide: Greeting, useClass: FormalGreeting, deps: []},
          ],
        });

        // The injector returns different instances.
        // See: {provide: ?, useExisting: ?} if you want the same instance.
        expect(injector.get(FormalGreeting)).not.toBe(injector.get(Greeting));
```

### [Multi-value example](#multi-value-example)

```
const locale = new InjectionToken<string[]>('locale');
        const injector = Injector.create({
          providers: [
            {provide: locale, multi: true, useValue: 'en'},
            {provide: locale, multi: true, useValue: 'sk'},
          ],
        });

        const locales: string[] = injector.get(locale);
        expect(locales).toEqual(['en', 'sk']);
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpRequestOptions
----

```
    
      interface HttpRequestOptions {}
    
    
```

@deprecated

Use `reportUploadProgress` and `reportDownloadProgress` instead

When set to `true` a request emited against the [`FetchBackend`](/api/common/http/FetchBackend) will throw an error.

This property accepts either a boolean to enable/disable transferring cache for eligible requests performed using [`HttpClient`](/api/common/http/HttpClient), or an object, which allows to configure cache parameters, such as which headers should be included (no headers are included by default).

Setting this property will override the options passed to `provideClientHydration()` for this particular request

Jump to details

----
url: https://angular.dev/api/core/MAX_ANIMATION_TIMEOUT
----

```
    
      const MAX_ANIMATION_TIMEOUT: InjectionToken<number>;
    
    
```

Jump to details

----
url: https://angular.dev/events/v21
----

## [Release Blog](#release-blog)

**Angular v21 is live**: check out the [v21 release blog](https://goo.gle/angular-v21-blog) to learn about all of the amazing new features coming your way.

## [Experience the v21 Release](#experience-the-v21-release)

Explore the Angular v21 release with this interactive game world. Curious how we made it? Click the `Show Code` button below to view the source code for this Angular app.

### v21 Game World

```
import {Component, computed, effect, inject, signal} from '@angular/core';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';

// 1. INTERFACES
interface Point {
  x: number;
  y: number;
}

interface Destination {
  id: string;
  name: string;
  position: Point;
  videoUrl: string;
  display: string;
}

interface RoadSegment {
  id: string;
  orientation: 'horizontal' | 'vertical';
  fixedCoordinate: number;
  start: number;
  end: number;
}

type WalkingDirection = 'left' | 'right' | 'up' | 'down' | null;

const STAND = 'assets/images/v21-event/mascot.png';
const WALK_LEFT_1 = 'assets/images/v21-event/mascot-left-1.png';
const WALK_LEFT_2 = 'assets/images/v21-event/mascot-left-2.png';
const WALK_RIGHT_1 = 'assets/images/v21-event/mascot-right-1.png';
const WALK_RIGHT_2 = 'assets/images/v21-event/mascot-right-2.png';
const WALK_UP_1 = 'assets/images/v21-event/mascot-up-1.png';
const WALK_UP_2 = 'assets/images/v21-event/mascot-up-2.png';
const WALK_DOWN_1 = 'assets/images/v21-event/mascot-down-1.png';
const WALK_DOWN_2 = 'assets/images/v21-event/mascot-down-2.png';

// Define all unique coordinates as constants for readability and maintanence.
const START_X = 0.45;
const LEFT_X = 0.19;
const RIGHT_X = 0.85;
const PALM_TREE_X = LEFT_X;
const RED_DOOR_X = LEFT_X; // Aligned with Palm Tree for a straight vertical path
const VOLCANO_X = RIGHT_X;
const CASTLE_X = RIGHT_X;

const BOTTOM_Y = 0.6;
const TOP_Y = 0.2;
const RED_DOOR_Y = TOP_Y;
const VOLCANO_Y = TOP_Y;
const PALM_TREE_Y = BOTTOM_Y;
const START_Y = BOTTOM_Y;
const CASTLE_Y = 0.7;

// 2. GAME DATA CONSTANTS
const STARTING_POINT: Point = {x: START_X, y: START_Y};

const DESTINATIONS: Destination[] = [
  {
    id: 'd1',
    name: 'Palm Tree',
    position: {x: PALM_TREE_X, y: PALM_TREE_Y},
    videoUrl: 'https://www.youtube.com/embed/FteCOhQb4Ow',
    display: "What's new in Angular AI",
  },
  {
    id: 'd2',
    name: 'Red Door',
    position: {x: RED_DOOR_X, y: RED_DOOR_Y},
    videoUrl: 'https://www.youtube.com/embed/Cegc5JtWbrI',
    display: 'Meet Angular Aria',
  },
  {
    id: 'd3',
    name: 'Volcano',
    position: {x: VOLCANO_X, y: VOLCANO_Y},
    videoUrl: 'https://www.youtube.com/embed/7v8mIW9_NXw',
    display: 'Introducing Signal Forms',
  },
  {
    id: 'd4',
    name: 'Castle',
    position: {x: CASTLE_X, y: CASTLE_Y},
    videoUrl: 'https://www.youtube.com/embed/wiWUpCsJ9Os',
    display: "Say hello to Angular's new Mascot!",
  },
];

const ALL_ROAD_SEGMENTS: RoadSegment[] = [
  // Road 1 (Dest 1 <-> Start)
  {
    id: 'r1',
    orientation: 'horizontal',
    fixedCoordinate: PALM_TREE_Y,
    start: PALM_TREE_X,
    end: START_X,
  },
  // Road 2 (Palm Tree <-> Red Door)
  {
    id: 'r2',
    orientation: 'vertical',
    fixedCoordinate: PALM_TREE_X,
    start: RED_DOOR_Y,
    end: PALM_TREE_Y,
  },
  // Road 3 (Dest 2 <-> Dest 3)
  {
    id: 'r3',
    orientation: 'horizontal',
    fixedCoordinate: RED_DOOR_Y,
    start: RED_DOOR_X,
    end: VOLCANO_X,
  },
  // Road 4 (Dest 3 <-> Dest 4)
  {id: 'r4', orientation: 'vertical', fixedCoordinate: VOLCANO_X, start: VOLCANO_Y, end: CASTLE_Y},
];

// 3. GAME MECHANICS CONSTANTS
const MOVE_STEP = 0.0025;
const ANIMATION_SPEED = 28; // Higher is slower. Update image every 10 frames.
// How far off a road's axis the character can be
const MOVE_TOLERANCE = 0.002;
// How close to a destination to "arrive" (must be very small)
const DESTINATION_TOLERANCE = 0.005;

@Component({
  selector: 'app-root',
  template: `
    <div
      class="game-container"
      [style.background-image]="'url(assets/images/v21-event/world-map.png)'"
    >
      <!-- Keys -->
      <div class="keys-container">
        @for (key of keysToShow(); track key) {
          <img
            src="assets/images/v21-event/key.png"
            class="key-icon"
            animate.enter="key-enter-animation"
          />
        }
        @if (showMascot()) {
          <img
            src="assets/images/v21-event/mascot.png"
            class="mascot-icon"
            animate.enter="key-enter-animation"
          />
        }
      </div>

      <!-- Character -->
      @if (!isDialogOpen()) {
        <div
          class="character"
          [style.left.%]="characterXPercent()"
          [style.top.%]="characterYPercent()"
          [style.background-image]="'url(' + characterImageUrl() + ')'"
        ></div>
      }

      <!-- Destinations -->
      @for (dest of destinations(); track dest.id) {
        <div
          class="destination-hotspot"
          [style.left.%]="dest.position.x * 100"
          [style.top.%]="dest.position.y * 100"
          [class.glowing]="activeDestination()?.id === dest.id"
        ></div>
      }

      <!-- D-Pad Controls -->
      <div class="d-pad">
        <button
          class="d-pad-button up"
          (mousedown)="handleButtonPress('ArrowUp')"
          (mouseup)="handleButtonRelease('ArrowUp')"
          (mouseleave)="handleButtonRelease('ArrowUp')"
          (touchstart)="handleButtonPress('ArrowUp')"
          (touchend)="handleButtonRelease('ArrowUp')"
        >
          &#x25B2;
        </button>
        <button
          class="d-pad-button left"
          (mousedown)="handleButtonPress('ArrowLeft')"
          (mouseup)="handleButtonRelease('ArrowLeft')"
          (mouseleave)="handleButtonRelease('ArrowLeft')"
          (touchstart)="handleButtonPress('ArrowLeft')"
          (touchend)="handleButtonRelease('ArrowLeft')"
        >
          &#x25C0;
        </button>
        <button
          class="d-pad-button right"
          (mousedown)="handleButtonPress('ArrowRight')"
          (mouseup)="handleButtonRelease('ArrowRight')"
          (mouseleave)="handleButtonRelease('ArrowRight')"
          (touchstart)="handleButtonPress('ArrowRight')"
          (touchend)="handleButtonRelease('ArrowRight')"
        >
          &#x25B6;
        </button>
        <button
          class="d-pad-button down"
          (mousedown)="handleButtonPress('ArrowDown')"
          (mouseup)="handleButtonRelease('ArrowDown')"
          (mouseleave)="handleButtonRelease('ArrowDown')"
          (touchstart)="handleButtonPress('ArrowDown')"
          (touchend)="handleButtonRelease('ArrowDown')"
        >
          &#x25BC;
        </button>
      </div>

      <!-- Info Sign -->
      @if (!isDialogOpen()) {
        <img [src]="infoSignImageUrl()" alt="Info Sign" class="info-sign" />
      }

      <!-- Dialog Box -->
      @if (isDialogOpen()) {
        <div class="dialog-overlay" (click)="closeDialog()">
          <div class="dialog-content" (click)="$event.stopPropagation()">
            <button class="close-icon" (click)="closeDialog()">&times;</button>
            <h2>{{ activeDestination()?.display }}</h2>
            @if (safeVideoUrl(); as url) {
              <iframe
                credentialless
                [src]="url"
                frameborder="0"
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; compute-pressure"
                allowfullscreen
              ></iframe>
            }
            <button class="close-button" (click)="closeDialog()">Close</button>
          </div>
        </div>
      }
      <!-- Explore Button -->
      @if (activeDestination() && (activeDestination()?.id !== 'd4' || allKeysCollected())) {
        <button class="explore-button" (click)="isDialogOpen.set(true)">Enter</button>
      }
    </div>
  `,
  styles: [
    `
      .keys-container {
        position: absolute;
        top: 1cqw;
        left: 1cqw;
        display: flex;
        z-index: 20;
      }

      .key-icon {
        width: 4cqw;
        height: 5cqw;
      }

      .mascot-icon {
        width: 4cqw;
        height: 5cqw;
        margin-left: 1cqw;
      }

      .key-enter-animation {
        animation: growIn 0.5s ease-in-out;
      }

      @keyframes growIn {
        from {
          transform: scale(0.1);
        }
        to {
          transform: scale(1);
        }
      }

      :host {
        display: block;
        width: 100%;
        height: 100%;
      }

      .game-container {
        width: 100%;
        aspect-ratio: 16 / 9;
        position: relative;
        overflow: hidden;
        background-size: 100% 100%;
        background-repeat: no-repeat;
        background-color: #3a3a3a; /* Fallback color */
        container-type: inline-size;
        container-name: game-container;
      }

      .character {
        z-index: 10;
        position: absolute;
        width: 9cqw;
        height: 9cqw;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
        transform: translate(-50%, -60%);
      }

      .destination-hotspot {
        position: absolute;
        width: 4cqw;
        height: 4cqw;
        border-radius: 50%;
        transform: translate(-50%, -10%);
        transition: all 0.3s ease;
      }

      .destination-hotspot.glowing {
        background-color: rgba(255, 0, 242, 0.5);
        box-shadow: 0 0 5px 15px rgba(255, 0, 242, 0.7);
      }

      .d-pad {
        position: absolute;
        bottom: 2cqw;
        left: 2cqw;
        width: 12cqw;
        height: 12cqw;
        display: grid;
        grid-template-areas:
          '. up .'
          'left . right'
          '. down .';
        grid-template-rows: 1fr 1fr 1fr;
        grid-template-columns: 1fr 1fr 1fr;
        gap: 0.5cqw;
        z-index: 20;
      }

      .d-pad-button {
        background-color: rgba(0, 0, 0, 0.5);
        border: none;
        border-radius: 0.5cqw;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        transition: background-color 0.2s;
        touch-action: manipulation; /* Prevent double tap zoom */
        color: white;
        font-size: 2.5cqw;
        font-weight: bold;
      }

      .d-pad-button:hover,
      .d-pad-button:active {
        background-color: rgba(0, 0, 0, 0.8);
      }

      .d-pad-button.up {
        grid-area: up;
      }
      .d-pad-button.down {
        grid-area: down;
      }
      .d-pad-button.left {
        grid-area: left;
      }
      .d-pad-button.right {
        grid-area: right;
      }

      .info-sign {
        position: absolute;
        top: 69%;
        left: 50%;
        transform: translate(-50%, 0%);
        width: 50cqw;
        height: 18cqw;
        object-fit: contain; /* Ensures the image fits within the bounds */
      }

      .dialog-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.6);
        z-index: 1000;
      }

      .dialog-content {
        position: absolute;
        top: 70%;
        left: 50%;
        transform: translate(-50%, -80%);
        background: white;
        border-radius: 5px;
        color: black;
        text-align: center;
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        width: 60%;
        height: 76%;
        padding: 1rem;
        box-sizing: border-box;
        display: flex;
        flex-direction: column;
        align-items: center;
      }

      .dialog-content h2 {
        margin-top: 0;
        margin-bottom: 1rem;
        font-family: 'Jersey 10', sans-serif;
      }

      .dialog-content iframe {
        width: 100%;
        flex-grow: 1;
        border: none;
        border-radius: 8px;
      }

      .close-button {
        margin-top: 1rem;
        padding: 10px 20px;
        border: none;
        background-color: #5c44e4;
        color: white;
        border-radius: 5px;
        cursor: pointer;
        font-size: 1rem;
        transition: background-color 0.3s ease;
      }

      .close-button:hover {
        background-color: #8514f5;
      }

      .close-icon {
        position: absolute;
        top: 10px;
        right: 10px;
        background: transparent;
        border: none;
        font-size: 1.5rem;
        cursor: pointer;
        color: #888;
        padding: 5px;
        line-height: 1;
      }

      .close-icon:hover {
        color: #000;
      }

      .explore-button {
        position: absolute;
        bottom: 2cqw;
        right: 2cqw;
        padding: 1.5cqw 3cqw;
        background-color: #e90464;
        color: white;
        border: 2px solid white; /* White border */
        border-radius: 1cqw;
        font-size: 2cqw;
        font-weight: bold;
        cursor: pointer;
        transition:
          background-color 0.2s,
          opacity 0.3s ease-in-out,
          visibility 0.3s ease-in-out;
        z-index: 20;
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
      }

      .explore-button:hover {
        background-color: #c20354;
      }

      /* When activeDestination() is true, the button is in the DOM */
      @if (activeDestination()) {
        .explore-button {
          opacity: 1;
          visibility: visible;
          pointer-events: auto;
        }
      }
    `,
  ],
  host: {
    '(window:keydown)': 'handleKeydown($event)',
    '(window:keyup)': 'handleKeyup($event)',
  },
})
export class App {
  characterPosition = signal<Point>(STARTING_POINT);
  isDialogOpen = signal<boolean>(false);
  visitedKeyDestinations = signal<Set<string>>(new Set());
  keysToShow = computed(() => Array.from(this.visitedKeyDestinations()));
  allKeysCollected = computed(() => this.keysToShow().length === 3);
  showMascot = signal(false);
  destinations = signal<Destination[]>(DESTINATIONS);
  walkingDirection = signal<WalkingDirection>(null);
  walkFrame = signal(0);
  characterImageUrl = computed(() => {
    if (this.activeDestination()) {
      return STAND;
    }

    const direction = this.walkingDirection();
    const frame = this.walkFrame();
    const animationFrame = Math.floor(frame / ANIMATION_SPEED) % 2;
    switch (direction) {
      case 'left':
        return animationFrame === 0 ? WALK_LEFT_1 : WALK_LEFT_2;
      case 'right':
        return animationFrame === 0 ? WALK_RIGHT_1 : WALK_RIGHT_2;
      case 'up':
        return animationFrame === 0 ? WALK_UP_1 : WALK_UP_2;
      case 'down':
        return animationFrame === 0 ? WALK_DOWN_1 : WALK_DOWN_2;
      default:
        return STAND;
    }
  });
  safeVideoUrl = signal<SafeResourceUrl | null>(null);
  pressedKeys = signal<Set<string>>(new Set());

  infoSignImageUrl = computed(() => {
    const activeDest = this.activeDestination();
    if (this.allKeysCollected() && this.showMascot()) {
      return 'assets/images/v21-event/congrats-sign.png';
    } else if (!activeDest) {
      return 'assets/images/v21-event/welcome-sign.png';
    } else if (activeDest.name === 'Castle') {
      return this.allKeysCollected()
        ? 'assets/images/v21-event/castle-sign.png'
        : 'assets/images/v21-event/entry-denied-sign.png';
    } else {
      return 'assets/images/v21-event/enter-sign.png';
    }
  });

  private sanitizer = inject(DomSanitizer);

  constructor() {
    effect(() => {
      const destination = this.activeDestination();
      if (destination?.videoUrl) {
        this.safeVideoUrl.set(this.sanitizer.bypassSecurityTrustResourceUrl(destination.videoUrl));
      } else {
        this.safeVideoUrl.set(null);
      }
    });

    this.gameLoop();
  }

  gameLoop() {
    const keys = this.pressedKeys();
    if (!this.isDialogOpen()) {
      const currentPos = this.characterPosition();
      let newPos = {...currentPos};
      let moveDirection: 'horizontal' | 'vertical' | null = null;

      if (keys.has('ArrowUp')) {
        newPos.y -= MOVE_STEP;
        moveDirection = 'vertical';
        this.walkingDirection.set('up');
      } else if (keys.has('ArrowDown')) {
        newPos.y += MOVE_STEP;
        moveDirection = 'vertical';
        this.walkingDirection.set('down');
      } else if (keys.has('ArrowLeft')) {
        newPos.x -= MOVE_STEP;
        moveDirection = 'horizontal';
        this.walkingDirection.set('left');
      } else if (keys.has('ArrowRight')) {
        newPos.x += MOVE_STEP;
        moveDirection = 'horizontal';
        this.walkingDirection.set('right');
      } else {
        this.walkingDirection.set(null);
      }

      if (moveDirection && this.isMoveAllowed(currentPos, newPos, moveDirection)) {
        this.characterPosition.set(newPos);
        this.walkFrame.update((frame) => frame + 1);
      }
    }

    requestAnimationFrame(() => this.gameLoop());
  }

  // 5. COMPUTED SIGNALS (DERIVED STATE)
  characterXPercent = computed(() => this.characterPosition().x * 100);
  characterYPercent = computed(() => this.characterPosition().y * 100);

  activeDestination = computed<Destination | null>(() => {
    const pos = this.characterPosition();
    for (const dest of this.destinations()) {
      const distance = Math.sqrt(
        Math.pow(pos.x - dest.position.x, 2) + Math.pow(pos.y - dest.position.y, 2),
      );
      if (distance < DESTINATION_TOLERANCE) {
        return dest;
      }
    }
    return null;
  });

  // 6. EVENT HANDLERS & METHODS
  handleKeydown(event: KeyboardEvent) {
    if (this.isDialogOpen() && event.key !== 'Escape') {
      return;
    }

    this.preventArrowDefault(event);
    this.pressedKeys.update((keys) => keys.add(event.key));

    if (
      event.key === 'Enter' &&
      this.activeDestination() &&
      (this.activeDestination()?.id !== 'd4' || this.allKeysCollected())
    ) {
      this.isDialogOpen.set(true);
    }
    if (event.key === 'Escape' && this.isDialogOpen()) {
      this.closeDialog();
    }
  }

  handleKeyup(event: KeyboardEvent) {
    this.preventArrowDefault(event);
    this.pressedKeys.update((keys) => {
      keys.delete(event.key);
      return keys;
    });
  }

  preventArrowDefault(event: KeyboardEvent) {
    const arrowKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
    if (arrowKeys.includes(event.key)) {
      event.preventDefault();
    }
  }

  handleButtonPress(key: string) {
    this.pressedKeys.update((keys) => keys.add(key));
  }

  handleButtonRelease(key: string) {
    this.pressedKeys.update((keys) => {
      keys.delete(key);
      return keys;
    });
  }

  closeDialog(): void {
    const activeDest = this.activeDestination();
    if (activeDest && ['d1', 'd2', 'd3'].includes(activeDest.id)) {
      this.visitedKeyDestinations.update((visited) => {
        if (!visited.has(activeDest.id)) {
          visited.add(activeDest.id);
          return new Set(visited); // Return new Set to trigger update
        }
        return visited;
      });
    }

    if (activeDest?.id === 'd4' && this.allKeysCollected()) {
      this.showMascot.set(true);
    }

    this.isDialogOpen.set(false);
  }

  private isMoveAllowed(
    currentPos: Point,
    newPos: Point,
    direction: 'horizontal' | 'vertical',
  ): boolean {
    // Find the road the character is currently on by checking the axis perpendicular to movement.
    let currentRoad: RoadSegment | null = null;
    let minDistance = Infinity;

    for (const road of ALL_ROAD_SEGMENTS) {
      if (direction === 'horizontal' && road.orientation === 'horizontal') {
        const distance = Math.abs(currentPos.y - road.fixedCoordinate);
        if (distance < minDistance) {
          minDistance = distance;
          currentRoad = road;
        }
      } else if (direction === 'vertical' && road.orientation === 'vertical') {
        const distance = Math.abs(currentPos.x - road.fixedCoordinate);
        if (distance < minDistance) {
          minDistance = distance;
          currentRoad = road;
        }
      }
    }

    // If no road is close enough, movement is not allowed.
    if (!currentRoad || minDistance > MOVE_TOLERANCE) {
      return false;
    }

    // Check if the new position is within the bounds of the identified road.
    if (currentRoad.orientation === 'horizontal') {
      return (
        newPos.x >= Math.min(currentRoad.start, currentRoad.end) - MOVE_TOLERANCE &&
        newPos.x <= Math.max(currentRoad.start, currentRoad.end) + MOVE_TOLERANCE
      );
    } else {
      // Vertical
      return (
        newPos.y >= Math.min(currentRoad.start, currentRoad.end) - MOVE_TOLERANCE &&
        newPos.y <= Math.max(currentRoad.start, currentRoad.end) + MOVE_TOLERANCE
      );
    }
  }
}
```

## [Angular v21 Developer Event \[Full Version\]](#angular-v21-developer-event-full-version)

Angular v21 is being delivered to you as a brand new release adventure. With modern AI tooling, performance updates and more, Angular v21 delivers fantastic new features to improve your developer experience. Whether you’re creating AI-powered apps or scalable enterprise applications, there has never been a better time to build with Angular.

🔥 What's coming in v21

* New Angular MCP Server tools to improve AI-powered workflows and code generation
* Your first look at Signal Forms, our new streamlined, signal-based approach to forms in Angular
* Exciting new details about the Angular Aria package

[](https://www.youtube.com/watch?v=DDAHORVzQ5g\&autoplay=1)

----
url: https://angular.dev/guide/animations/migration
----

The `@angular/animations` package is deprecated as of v20.2, which also introduced the new `animate.enter` and `animate.leave` feature to add animations to your application. Using these new features, you can replace all animations based on `@angular/animations` with plain CSS or JS animation libraries. Removing `@angular/animations` from your application can significantly reduce the size of your JavaScript bundle. Native CSS animations generally offer superior performance, as they can benefit from hardware acceleration. This guide walks through the process of refactoring your code from `@angular/animations` to native CSS animations.

## [How to write animations in native CSS](#how-to-write-animations-in-native-css)

If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here are a few of them:\
[MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations)\
[W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp)\
[The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/)\
[CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)

and a couple of videos:\
[Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw)\
[Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU\&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5)

Check out some of these guides and tutorials, then come back to this guide.

## [Creating Reusable Animations](#creating-reusable-animations)

Just like with the animations package, you can create reusable animations that can be shared across your application. The animations package version of this had you using the [`animation()`](/api/animations/animation) function in a shared typescript file. The native CSS version of this is similar, but lives in a shared CSS file.

#### [With Animations Package](#with-animations-package)

### animations.ts

```
export const sharedAnimation = animation([
  style({
    height: 0,
    opacity: 1,
    backgroundColor: 'red',
  }),
  animate('1s'),
]);
```

#### [With Native CSS](#with-native-css)

### animations.css

```
@keyframes sharedAnimation {
  to {
    height: 0;
    opacity: 1;
    background-color: 'red';
  }
}

.animated-class {
  animation: sharedAnimation 1s;
}
```

Adding the class `animated-class` to an element would trigger the animation on that element.

## [Animating a Transition](#animating-a-transition)

### [Animating State and Styles](#animating-state-and-styles)

The animations package allowed you to define various states using the [`state()`](api/animations/state) function within a component. Examples might be an `open` or `closed` state containing the styles for each respective state within the definition. For example:

#### [With Animations Package](#animating-state-and-styles-with-animations-package)

### open-close.ts

```
// ...
state(
  'open',
  style({
    height: '200px',
    opacity: 1,
    backgroundColor: 'yellow',
  }),
),
```

This same behavior can be accomplished natively by using CSS classes, either with a keyframe animation or transition styling.

#### [With Native CSS](#animating-state-and-styles-with-native-css)

### animations.css

```
.open {
  height: '200px';
  opacity: 1;
  background-color: 'yellow';
  transition: all 1s;
}

.closed {
  height: '100px';
  opacity: 0.8;
  background-color: 'blue';
  transition: all 1s;
}
```

Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings).

You can see similar examples in the template guide for [animating styles directly](guide/templates/binding#css-style-properties).

### [Transitions, Timing, and Easing](#transitions-timing-and-easing)

The animations package [`animate()`](/api/animations/animate) function allows for providing timing, like duration, delays and easing. This can be done natively with CSS using several css properties or shorthand properties.

Specify `animation-duration`, `animation-delay`, and `animation-timing-function` for a keyframe animation in CSS, or alternatively use the `animation` shorthand property.

### animations.css

```
.example-element {
  animation-duration: 1s;
  animation-delay: 500ms;
  animation-timing-function: ease-in-out;
}

.example-shorthand {
  animation: exampleAnimation 1s ease-in-out 500ms;
}
```

Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`.

### animations.css

```
.example-element {
  transition-duration: 1s;
  transition-delay: 500ms;
  transition-timing-function: ease-in-out;
  transition-property: margin-right;
}

.example-shorthand {
  transition: margin-right 1s ease-in-out 500ms;
}
```

### [Triggering an Animation](#triggering-an-animation)

The animations package required specifying triggers using the [`trigger()`](/api/animations/trigger) function and nesting all of your states within it. With native CSS, this is unnecessary. Animations can be triggered by toggling CSS styles or classes. Once a class is present on an element, the animation will occur. Removing the class will revert the element back to whatever CSS is defined for that element. This results in significantly less code to do the same animation. Here's an example:

#### [With Animations Package](#triggering-an-animation-with-animations-package)

### open-close.ts

```
import {Component, signal} from '@angular/core';
import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';

@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      state(
        'open',
        style({
          height: '200px',
          opacity: 1,
          backgroundColor: 'yellow',
        }),
      ),
      state(
        'closed',
        style({
          height: '100px',
          opacity: 0.5,
          backgroundColor: 'green',
        }),
      ),
      // ...
      transition('* => *', [
        animate(
          '1s',
          keyframes([
            style({opacity: 0.1, offset: 0.1}),
            style({opacity: 0.6, offset: 0.2}),
            style({opacity: 1, offset: 0.5}),
            style({opacity: 0.2, offset: 0.7}),
          ]),
        ),
      ]),
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrl: 'open-close.css',
})
export class OpenClose {
  isOpen = signal(false);

  toggle() {
    this.isOpen.update((isOpen) => !isOpen);
  }
}
```

### open-close.html

```
<nav>
  <button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen() ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
```

### open-close.css

```
:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  color: #000000;
  font-weight: bold;
  font-size: 20px;
}
```

#### [With Native CSS](#triggering-an-animation-with-native-css)

### open-close.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-open-close',
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  isOpen = signal(true);
  toggle() {
    this.isOpen.update((isOpen) => !isOpen);
  }
}
```

### open-close.html

```
<h2>Open / Close Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>

<div class="open-close-container" [class.open]="isOpen()">
  <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
```

### open-close.css

```
:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  font-weight: bold;
  font-size: 20px;
  height: 100px;
  opacity: 0.8;
  background: #3b82f6;
  color: #ebebeb;
  transition-property: height, opacity, background-color, color;
  transition-duration: 1s;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

.open {
  transition-duration: 0.5s;
  height: 200px;
  opacity: 1;
  background: #475569;
  color: #f9fafb;
}
```

## [Transition and Triggers](#transition-and-triggers)

### [Predefined State and wildcard matching](#predefined-state-and-wildcard-matching)

The animations package offers the ability to match your defined states to a transition via strings. For example, animating from open to closed would be `open => closed`. You can use wildcards to match any state to a target state, like `* => closed` and the `void` keyword can be used for entering and exiting states. For example: `* => void` for when an element leaves a view or `void => *` for when the element enters a view.

These state matching patterns are not needed at all when animating with CSS directly. You can manage what transitions and `@keyframes` animations apply based on whatever classes you set and / or styles you set on the elements. You can also add `@starting-style` to control how the element looks upon immediately entering the DOM.

### [Automatic Property Calculation with Wildcards](#automatic-property-calculation-with-wildcards)

The animations package offers the ability to animate things that have been historically difficult to animate, like animating a set height to `height: auto`. You can now do this with pure CSS as well.

#### [With Animations Package](#automatic-property-calculation-with-animations-package)

### auto-height.ts

```
import {Component, signal} from '@angular/core';
import {trigger, transition, state, animate, style} from '@angular/animations';

@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      state('true', style({height: '*'})),
      state('false', style({height: '0px'})),
      transition('false <=> true', animate(1000)),
    ]),
  ],
  templateUrl: 'auto-height.html',
  styleUrl: 'auto-height.css',
})
export class AutoHeight {
  isOpen = signal(false);

  toggle() {
    this.isOpen.update((isOpen) => !isOpen);
  }
}
```

### auto-height.html

```
<h2>Auto Height Example</h2>

<button type="button" (click)="toggle()">Toggle Open/Close</button>

<div class="container" [@openClose]="isOpen() ? true : false">
  <div class="content">
    <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
  </div>
</div>
```

### auto-height.css

```
.container {
  display: block;
  overflow: hidden;
}

.container .content {
  padding: 20px;
  margin-top: 1em;
  font-weight: bold;
  font-size: 20px;
  background: #3b82f6;
  color: #ebebeb;
}
```

You can use CSS Grid to animate to auto height.

#### [With Native CSS](#automatic-property-calculation-with-native-css)

### auto-height.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-auto-height',
  templateUrl: 'auto-height.html',
  styleUrls: ['auto-height.css'],
})
export class AutoHeight {
  isOpen = signal(true);
  toggle() {
    this.isOpen.update((isOpen) => !isOpen);
  }
}
```

### auto-height.html

```
<h2>Auto Height Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>

<div class="container" [class.open]="isOpen()">
  <div class="content">
    <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
  </div>
</div>
```

### auto-height.css

```
.container {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows 1s;
}

.container.open {
  grid-template-rows: 1fr;
}

.container .content {
  min-height: 0;
  transition: visibility 1s;
  padding: 0 20px;
  visibility: hidden;
  margin-top: 1em;
  font-weight: bold;
  font-size: 20px;
  background: #3b82f6;
  color: #ebebeb;
  overflow: hidden;
}

.container.open .content {
  visibility: visible;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and (this tutorial)\[<https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/]> for more information.

### [Animate entering and leaving a view](#animate-entering-and-leaving-a-view)

The animations package offered the previously mentioned pattern matching for entering and leaving but also included the shorthand aliases of `:enter` and `:leave`.

#### [With Animations Package](#enter-and-leave-with-animations-package)

### insert-remove.ts

```
import {Component} from '@angular/core';
import {trigger, transition, animate, style} from '@angular/animations';

@Component({
  selector: 'app-insert-remove',
  animations: [
    trigger('myInsertRemoveTrigger', [
      transition(':enter', [style({opacity: 0}), animate('200ms', style({opacity: 1}))]),
      transition(':leave', [animate('200ms', style({opacity: 0}))]),
    ]),
  ],
  templateUrl: 'insert-remove.html',
  styleUrls: ['insert-remove.css'],
})
export class InsertRemove {
  isShown = false;

  toggle() {
    this.isShown = !this.isShown;
  }
}
```

### insert-remove.html

```
<h2>Insert/Remove</h2>

<nav>
  <button type="button" (click)="toggle()">Toggle Insert/Remove</button>
</nav>

@if (isShown) {
  <div @myInsertRemoveTrigger class="insert-remove-container">
    <p>The box is inserted</p>
  </div>
}
```

### insert-remove.css

```
:host {
  display: block;
}

.insert-remove-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  color: #000000;
  font-weight: bold;
  font-size: 20px;
}
```

#### [With Native CSS](#enter-with-native-css)

### insert.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-insert',
  templateUrl: 'insert.html',
  styleUrls: ['insert.css'],
})
export class Insert {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### insert.html

```
<h2>Insert Element Example</h2>

<nav>
  <button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>

@if (isShown()) {
  <div class="insert-container" animate.enter="enter-animation">
    <p>The box is inserted</p>
  </div>
}
```

### insert.css

```
:host {
  display: block;
}

.insert-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
}

.insert-container p {
  margin: 0;
}

.enter-animation {
  animation: slide-fade 1s;
}

@keyframes slide-fade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

#### [With Native CSS](#leave-with-native-css)

### remove.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-remove',
  templateUrl: 'remove.html',
  styleUrls: ['remove.css'],
})
export class Remove {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### remove.html

```
<h2>Remove Element Example</h2>

<nav>
  <button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>

@if (isShown()) {
  <div class="insert-container" animate.leave="deleting">
    <p>The box is inserted</p>
  </div>
}
```

### remove.css

```
:host {
  display: block;
}

.insert-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.insert-container p {
  margin: 0;
}

.deleting {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

For more information on `animate.enter` and `animate.leave`, see the [Enter and Leave animations guide](guide/animations).

### [Animating increment and decrement](#animating-increment-and-decrement)

Along with the aforementioned `:enter` and `:leave`, there's also `:increment` and `:decrement`. You can animate these also by adding and removing classes. Unlike the animation package built-in aliases, there is no automatic application of classes when the values go up or down. You can apply the appropriate classes programmatically. Here's an example:

#### [With Animations Package](#increment-and-decrement-with-animations-package)

### increment-decrement.ts

```
import {Component, signal} from '@angular/core';
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';

@Component({
  selector: 'app-increment-decrement',
  templateUrl: 'increment-decrement.html',
  styleUrls: ['increment-decrement.css'],
  animations: [
    trigger('incrementAnimation', [
      transition(':increment', [
        animate('300ms ease-out', style({color: 'green', transform: 'scale(1.3, 1.2)'})),
      ]),
      transition(':decrement', [
        animate('300ms ease-out', style({color: 'red', transform: 'scale(0.8, 0.9)'})),
      ]),
    ]),
  ],
})
export class IncrementDecrement {
  num = signal(0);

  modify(n: number) {
    this.num.update((v) => (v += n));
  }
}
```

### increment-decrement.html

```
<h3>Increment and Decrement Example</h3>
<section>
  <p [@incrementAnimation]="num()">Number {{ num() }}</p>
  <div class="controls">
    <button type="button" (click)="modify(1)">+</button>
    <button type="button" (click)="modify(-1)">-</button>
  </div>
</section>
```

### increment-decrement.css

```
:host {
  display: block;
  font-size: 32px;
  margin: 20px;
  text-align: center;
}

section {
  border: 1px solid lightgray;
  border-radius: 50px;
}

p {
  display: inline-block;
  margin: 2rem 0;
  text-transform: uppercase;
}

.controls {
  padding-bottom: 2rem;
}

button {
  font: inherit;
  border: 0;
  background: lightgray;
  width: 50px;
  border-radius: 10px;
}

button + button {
  margin-left: 10px;
}
```

#### [With Native CSS](#increment-and-decrement-with-native-css)

### increment-decrement.ts

```
import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';

@Component({
  selector: 'app-increment-decrement',
  templateUrl: 'increment-decrement.html',
  styleUrls: ['increment-decrement.css'],
})
export class IncrementDecrement implements OnInit {
  num = signal(0);
  el = viewChild<ElementRef<HTMLParagraphElement>>('el');

  ngOnInit() {
    this.el()?.nativeElement.addEventListener('animationend', (ev) => {
      if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) {
        this.animationFinished();
      }
    });
  }

  modify(n: number) {
    const targetClass = n > 0 ? 'increment' : 'decrement';
    this.num.update((v) => (v += n));
    this.el()?.nativeElement.classList.add(targetClass);
  }

  animationFinished() {
    this.el()?.nativeElement.classList.remove('increment', 'decrement');
  }

  ngOnDestroy() {
    this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished);
  }
}
```

### increment-decrement.html

```
<h3>Increment and Decrement Example</h3>
<section>
  <p #el>Number {{ num() }}</p>
  <div class="controls">
    <button type="button" (click)="modify(1)">+</button>
    <button type="button" (click)="modify(-1)">-</button>
  </div>
</section>
```

### increment-decrement.css

```
:host {
  display: block;
  font-size: 32px;
  margin: 20px;
  text-align: center;
}

section {
  border: 1px solid lightgray;
  border-radius: 50px;
}

p {
  display: inline-block;
  margin: 2rem 0;
  text-transform: uppercase;
}

.increment {
  animation: increment 300ms;
}

.decrement {
  animation: decrement 300ms;
}

.controls {
  padding-bottom: 2rem;
}

button {
  font: inherit;
  border: 0;
  background: lightgray;
  width: 50px;
  border-radius: 10px;
}

button + button {
  margin-left: 10px;
}

@keyframes increment {
  33% {
    color: green;
    transform: scale(1.3, 1.2);
  }
  66% {
    color: green;
    transform: scale(1.2, 1.2);
  }
  100% {
    transform: scale(1, 1);
  }
}

@keyframes decrement {
  33% {
    color: red;
    transform: scale(0.8, 0.9);
  }
  66% {
    color: red;
    transform: scale(0.9, 0.9);
  }
  100% {
    transform: scale(1, 1);
  }
}
```

### [Parent / Child Animations](#parent---child-animations)

Unlike the animations package, when multiple animations are specified within a given component, no animation has priority over another and nothing blocks any animation from firing. Any sequencing of animations would have to be handled by your definition of your CSS animation, using animation / transition delay, and / or using `animationend` or `transitionend` to handle adding the next css to be animated.

Child animations fire only within the same component template. In the `@angular/animations` package, parent animations could query and trigger animations in nested child components using [`query()`](/api/animations/query) and [`animateChild()`](/api/animations/animateChild). With `animate.leave` and native CSS animations, animations defined inside nested child component templates will not fire when a parent component removes an element or view. Only nested animations within the same Angular component template will execute. See the [Enter and Leave Animations guide](guide/animations#element-removal-order) for more information on this.

### [Disabling an animation or all animations](#disabling-an-animation-or-all-animations)

With native CSS animations, if you'd like to disable the animations that you've specified, you have multiple options.

1. Create a custom class that forces animation and transition to `none`.

```
.no-animation {
  animation: none !important;
  transition: none !important;
}
```

Applying this class to an element prevents any animation from firing on that element. You could alternatively scope this to your entire DOM or section of your DOM to enforce this behavior. However, this prevents animation events from firing. If you are awaiting animation events for element removal, this solution won't work. A workaround is to set durations to 1 millisecond instead.

2. Use the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query to ensure no animations play for users that prefer less animation.

3. Prevent adding animation classes programmatically

### [Animation Callbacks](#animation-callbacks)

The animations package exposed callbacks for you to use in the case that you want to do something when the animation has finished. Native CSS animations also have these callbacks.

The animations package has built-in functionality for creating complex sequences. These sequences are all entirely possible without the animations package.

### [Targeting specific elements](#targeting-specific-elements)

In the animations package, you could target specific elements by using the [`query()`](/api/animations/query) function to find specific elements by a CSS class name, similar to [`document.querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). This is unnecessary in a native CSS animation world. Instead, you can use your CSS selectors to target sub-classes and apply any desired `transform` or `animation`.

To toggle classes for child nodes within a template, you can use class and style bindings to add the animations at the right points.

### [Stagger()](#stagger)

The [`stagger()`](/api/animations/stagger) function allowed you to delay the animation of each item in a list of items by a specified time to create a cascade effect. You can replicate this behavior in native CSS by utilizing `animation-delay` or `transition-delay`. Here is an example of what that CSS might look like.

#### [With Animations Package](#stagger-with-animations-package)

### stagger.ts

```
import {Component, HostBinding, signal} from '@angular/core';
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';

@Component({
  selector: 'app-stagger',
  templateUrl: 'stagger.html',
  styleUrls: ['stagger.css'],
  animations: [
    trigger('pageAnimations', [
      transition(':enter', [
        query('.item', [
          style({opacity: 0, transform: 'translateY(-10px)'}),
          stagger(200, [animate('500ms ease-in', style({opacity: 1, transform: 'none'}))]),
        ]),
      ]),
    ]),
  ],
})
export class Stagger {
  @HostBinding('@pageAnimations')
  items = [1, 2, 3];
}
```

### stagger.html

```
<h2>Stagger Example</h2>

<ul class="items">
  @for (item of items; track item) {
    <li class="item">{{ item }}</li>
  }
</ul>
```

### stagger.css

```
.items {
  list-style: none;
  padding: 0;
  margin: 0;
}
```

#### [With Native CSS](#stagger-with-native-css)

### stagger.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-stagger',
  templateUrl: './stagger.html',
  styleUrls: ['stagger.css'],
})
export class Stagger {
  show = signal(true);
  items = [1, 2, 3];

  refresh() {
    this.show.set(false);
    setTimeout(() => {
      this.show.set(true);
    }, 10);
  }
}
```

### stagger.html

```
<h1>Stagger Example</h1>
<button type="button" class="toggle-btn" (click)="refresh()">Refresh</button>
<div class="items-container">
  @if (show()) {
    <ul class="items">
      @for (item of items; track $index) {
        <li class="item" style="--index: {{ $index }}">{{ item }}</li>
      }
    </ul>
  }
</div>
```

### stagger.css

```
.items-container {
  min-height: 4.5rem;
}

.items {
  list-style: none;
  padding: 0;
  margin: 0;
}

.items .item {
  transition-property: opacity, transform;
  transition-duration: 500ms;
  transition-delay: calc(200ms * var(--index));

  @starting-style {
    opacity: 0;
    transform: translateX(-10px);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

### [Parallel Animations](#parallel-animations)

The animations package has a `group()` function to play multiple animations at the same time. In CSS, you have full control over animation timing. If you have multiple animations defined, you can apply all of them at once.

```
.target-element {
  animation:
    rotate 3s,
    fade-in 2s;
}
```

In this example, the `rotate` and `fade-in` animations fire at the same time.

### [Animating the items of a reordering list](#animating-the-items-of-a-reordering-list)

Items reordering in a list works out of the box using the previously described techniques. No additional special work is required. Items in a `@for` loop will be removed and re-added properly, which will fire off animations using `@starting-styles` for entry animations. Alternatively, you can use `animate.enter` for this same behavior. Use `animate.leave` to animate elements as they are removed, as seen in the example above.

#### [With Animations Package](#reordering-list-with-animations-package)

### reorder.ts

```
import {Component, signal} from '@angular/core';
import {trigger, transition, animate, query, style} from '@angular/animations';

@Component({
  selector: 'app-reorder',
  templateUrl: './reorder.html',
  styleUrls: ['reorder.css'],
  animations: [
    trigger('itemAnimation', [
      transition(':enter', [
        style({opacity: 0, transform: 'translateX(-10px)'}),
        animate('300ms', style({opacity: 1, transform: 'translateX(none)'})),
      ]),
      transition(':leave', [
        style({opacity: 1, transform: 'translateX(none)'}),
        animate('300ms', style({opacity: 0, transform: 'translateX(-10px)'})),
      ]),
    ]),
  ],
})
export class Reorder {
  show = signal(true);
  items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];

  randomize() {
    const randItems = [...this.items];
    const newItems = [];
    for (let i of this.items) {
      const max: number = this.items.length - newItems.length;
      const randNum = Math.floor(Math.random() * max);
      newItems.push(...randItems.splice(randNum, 1));
    }

    this.items = newItems;
  }
}
```

### reorder.html

```
<h1>Reordering List Example</h1>
<button type="button" (click)="randomize()">Randomize</button>

<ul class="items">
  @for (item of items; track item) {
    <li @itemAnimation class="item">{{ item }}</li>
  }
</ul>
```

### reorder.css

```
.items {
  list-style: none;
  padding: 0;
  margin: 0;
}
```

#### [With Native CSS](#reordering-list-with-native-css)

### reorder.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-reorder',
  templateUrl: './reorder.html',
  styleUrls: ['reorder.css'],
})
export class Reorder {
  show = signal(true);
  items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];

  randomize() {
    const randItems = [...this.items];
    const newItems = [];
    for (let i of this.items) {
      const max: number = this.items.length - newItems.length;
      const randNum = Math.floor(Math.random() * max);
      newItems.push(...randItems.splice(randNum, 1));
    }

    this.items = newItems;
  }
}
```

### reorder.html

```
<h1>Reordering List Example</h1>
<button type="button" class="toggle-btn" (click)="randomize()">Randomize</button>

<ul class="items">
  @for (item of items; track item) {
    <li class="item" animate.leave="fade">{{ item }}</li>
  }
</ul>
```

### reorder.css

```
.items {
  list-style: none;
  padding: 0;
  margin: 0;
}

.items .item {
  transition-property: opacity, transform;
  transition-duration: 500ms;

  @starting-style {
    opacity: 0;
    transform: translateX(-10px);
  }
}

.items .item.fade {
  animation: fade-out 500ms;
}

@keyframes fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

## [Migrating usages of AnimationPlayer](#migrating-usages-of-animationplayer)

The [`AnimationPlayer`](/api/animations/AnimationPlayer) class allows access to an animation to do more advanced things like pause, play, restart, and finish an animation through code. All of these things can be handled natively as well.

You can retrieve animations off an element directly using [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). This returns an array of every [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) on that element. You can use the `Animation` API to do much more than you could with what the [`AnimationPlayer`](/api/animations/AnimationPlayer) from the animations package offered. From here you can `cancel()`, `play()`, `pause()`, `reverse()` and much more. This native API should provide everything you need to control your animations.

## [Route Transitions](#route-transitions)

You can use view transitions to animate between routes. See the [Route Transition Animations Guide](guide/routing/route-transition-animations) to get started.

----
url: https://angular.dev/reference/migrations/inject-function
----

Angular's [`inject`](/api/core/inject) function offers more accurate types and better compatibility with standard decorators, compared to constructor-based injection.

This schematic converts constructor-based injection in your classes to use the [`inject`](/api/core/inject) function instead.

Run the schematic using the following command:

```
ng generate @angular/core:inject
```

#### [Before](#before)

```
import {Component, Inject, Optional} from '@angular/core';
import {MyService} from './service';
import {DI_TOKEN} from './token';

@Component({template: ''})
export class MyComp {
  constructor(
    private service: MyService,
    @Inject(DI_TOKEN) @Optional() readonly token: string,
  ) {}
}
```

#### [After](#after)

```
import {Component, inject} from '@angular/core';
import {MyService} from './service';
import {DI_TOKEN} from './token';

@Component({template: ''})
export class MyComp {
  private service = inject(MyService);
  readonly token = inject(DI_TOKEN, {optional: true});
}
```

## [Migration options](#migration-options)

The migration includes several options to customize its output.

### [`path`](#path)

Determines which sub-path in your project should be migrated. Pass in `.` or leave it blank to migrate the entire directory.

### [`migrateAbstractClasses`](#migrateabstractclasses)

Angular doesn't validate that parameters of abstract classes are injectable. This means that the migration can't reliably migrate them to [`inject`](/api/core/inject) without risking breakages which is why they're disabled by default. Enable this option if you want abstract classes to be migrated, but note that you may have to **fix some breakages manually**.

### [`backwardsCompatibleConstructors`](#backwardscompatibleconstructors)

By default the migration tries to clean up the code as much as it can, which includes deleting parameters from the constructor, or even the entire constructor if it doesn't include any code. In some cases this can lead to compilation errors when classes with Angular decorators inherit from other classes with Angular decorators. If you enable this option, the migration will generate an additional constructor signature to keep it backwards compatible, at the expense of more code.

#### [Before](#backwards-compatible-constructors-before)

```
import {Component} from '@angular/core';
import {MyService} from './service';

@Component({template: ''})
export class MyComp {
  constructor(private service: MyService) {}
}
```

#### [After](#backwards-compatible-constructors-after)

```
import {Component, inject} from '@angular/core';
import {MyService} from './service';

@Component({template: ''})
export class MyComp {
  private service = inject(MyService);

  /** Inserted by Angular inject() migration for backwards compatibility */
  constructor(...args: unknown[]);

  constructor() {}
}
```

### [`nonNullableOptional`](#nonnullableoptional)

If injection fails for a parameter with the [`@Optional`](/api/core/Optional) decorator, Angular returns `null` which means that the real type of any [`@Optional`](/api/core/Optional) parameter will be `| null`. However, because decorators cannot influence their types, there is a lot of existing code whose type is incorrect. The type is fixed in `inject()` which can cause new compilation errors to show up. If you enable this option, the migration will produce a non-null assertion after the `inject()` call to match the old type, at the expense of potentially hiding type errors.

**NOTE:** non-null assertions won't be added to parameters that are already typed to be nullable, because the code that depends on them likely already accounts for their nullability.

#### [Before](#non-nullable-optional-before)

```
import {Component, Inject, Optional} from '@angular/core';
import {TOKEN_ONE, TOKEN_TWO} from './token';

@Component({template: ''})
export class MyComp {
  constructor(
    @Inject(TOKEN_ONE) @Optional() private tokenOne: number,
    @Inject(TOKEN_TWO) @Optional() private tokenTwo: string | null,
  ) {}
}
```

#### [After](#non-nullable-optional-after)

```
import {Component, inject} from '@angular/core';
import {TOKEN_ONE, TOKEN_TWO} from './token';

@Component({template: ''})
export class MyComp {
  // Note the `!` at the end.
  private tokenOne = inject(TOKEN_ONE, {optional: true})!;

  // Does not have `!` at the end, because the type was already nullable.
  private tokenTwo = inject(TOKEN_TWO, {optional: true});
}
```

----
url: https://angular.dev/api/core/AfterRenderOptions
----

## [API](/api/core/AfterRenderOptions#api)

```
    
      interface AfterRenderOptions {}
    
    
```

The [`Injector`](/api/core/Injector) to use during creation.

If this is not provided, the current injection context will be used instead (via [`inject`](/api/core/inject)).

Whether the hook should require manual cleanup.

If this is `false` (the default) the hook will automatically register itself to be cleaned up with the current [`DestroyRef`](/api/core/DestroyRef).

Jump to details

----
url: https://angular.dev/guide/forms/signals/comparison
----

Angular provides three approaches to building forms: Signal Forms, Reactive Forms, and Template-driven Forms. Each has distinct patterns for managing state, validation, and data flow. This guide helps you understand the differences and choose the right approach for your project.

## [Quick comparison](#quick-comparison)

| Feature          | Signal Forms                       | Reactive Forms                                                              | Template-driven Forms   |
| ---------------- | ---------------------------------- | --------------------------------------------------------------------------- | ----------------------- |
| Source of truth  | User-defined writable signal model | [`FormControl`](/api/forms/FormControl)/[`FormGroup`](/api/forms/FormGroup) | User model in component |
| Type safety      | Inferred from model                | Explicit with typed forms                                                   | Minimal                 |
| Validation       | Schema with path-based validators  | List of validators passed to Controls                                       | Directive-based         |
| State management | Signal-based                       | Observable-based                                                            | Angular-managed         |
| Setup            | Signal + schema function           | FormControl tree                                                            | NgModel in template     |
| Best for         | Signal-based apps                  | Complex forms                                                               | Simple forms            |
| Learning curve   | Medium                             | Medium-High                                                                 | Low                     |
| Status           | Stable (v22+)                      | Stable                                                                      | Stable                  |

## [By example: Login form](#by-example-login-form)

The best way to understand the differences is to see the same form implemented in all three approaches.

### Signal forms

```
import {Component, signal} from '@angular/core';
import {
  email,
  form,
  FormField,
  FormRoot,
  minLength,
  required,
  submit,
} from '@angular/forms/signals';

@Component({
  selector: 'app-login',
  imports: [FormField, FormRoot],
  template: `
    <form [formRoot]="loginForm" (submit)="onSubmit()">
      <div>
        <label>
          Email
          <input type="email" [formField]="loginForm.email" />
        </label>
        @if (loginForm.email().touched() && loginForm.email().invalid()) {
          <span class="error">
            {{ loginForm.email().errors()[0].message }}
          </span>
        }
      </div>

      <div>
        <label>
          Password
          <input type="password" [formField]="loginForm.password" />
        </label>
        @if (loginForm.password().touched() && loginForm.password().invalid()) {
          <span class="error">
            {{ loginForm.password().errors()[0].message }}
          </span>
        }
      </div>

      <button type="submit" [disabled]="loginForm().invalid()">Sign In</button>
    </form>
  `,
})
export class LoginComponent {
  loginModel = signal({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel, (fieldPath) => {
    required(fieldPath.email, {message: 'Email is required'});
    email(fieldPath.email, {message: 'Enter a valid email address'});

    required(fieldPath.password, {message: 'Password is required'});
    minLength(fieldPath.password, 8, {message: 'Password must be at least 8 characters'});
  });

  onSubmit() {
    // Only runs the action when the form is valid.
    submit(this.loginForm, async (field) => {
      console.log('Submitting:', field().value());
    });
  }
}
```

### Reactive forms

```
import {Component} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';

@Component({
  selector: 'app-login',
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="loginForm" (submit)="onSubmit()">
      <div>
        <label>
          Email
          <input type="email" formControlName="email" />
        </label>
        @if (loginForm.controls.email.touched && loginForm.controls.email.invalid) {
          <span class="error">
            @if (loginForm.controls.email.errors?.['required']) {
              Email is required
            }
            @if (loginForm.controls.email.errors?.['email']) {
              Enter a valid email address
            }
          </span>
        }
      </div>

      <div>
        <label>
          Password
          <input type="password" formControlName="password" />
        </label>
        @if (loginForm.controls.password.touched && loginForm.controls.password.invalid) {
          <span class="error">
            @if (loginForm.controls.password.errors?.['required']) {
              Password is required
            }
            @if (loginForm.controls.password.errors?.['minlength']) {
              Password must be at least 8 characters
            }
          </span>
        }
      </div>

      <button type="submit" [disabled]="loginForm.invalid">Sign In</button>
    </form>
  `,
})
export class LoginComponent {
  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required, Validators.minLength(8)]),
  });

  onSubmit() {
    if (this.loginForm.valid) {
      const credentials = this.loginForm.value;
      console.log('Submitting:', credentials);
    }
  }
}
```

### Template-driven forms

```
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-login',
  imports: [FormsModule],
  template: `
    <form #loginForm="ngForm" (submit)="onSubmit()">
      <div>
        <label>
          Email
          <input
            type="email"
            name="email"
            [(ngModel)]="email"
            #emailInput="ngModel"
            required
            email
          />
        </label>
        @if (emailInput.touched && emailInput.invalid) {
          <span class="error">
            @if (emailInput.errors?.['required']) {
              Email is required
            }
            @if (emailInput.errors?.['email']) {
              Enter a valid email address
            }
          </span>
        }
      </div>

      <div>
        <label>
          Password
          <input
            type="password"
            name="password"
            [(ngModel)]="password"
            #passwordInput="ngModel"
            required
            minlength="8"
          />
        </label>
        @if (passwordInput.touched && passwordInput.invalid) {
          <span class="error">
            @if (passwordInput.errors?.['required']) {
              Password is required
            }
            @if (passwordInput.errors?.['minlength']) {
              Password must be at least 8 characters
            }
          </span>
        }
      </div>

      <button type="submit" [disabled]="loginForm.invalid">Sign In</button>
    </form>
  `,
})
export class LoginComponent {
  email = '';
  password = '';

  onSubmit() {
    const credentials = {
      email: this.email,
      password: this.password,
    };
    console.log('Submitting:', credentials);
  }
}
```

## [Understanding the differences](#understanding-the-differences)

The three approaches make different design choices that affect how you write and maintain your forms. These differences stem from where each approach stores form state and how it manages validation.

### [Where your form data lives](#where-your-form-data-lives)

The most fundamental difference is where each approach considers the "source of truth" for form values.

Signal Forms stores data in a writable signal. When you need the current form values, you call the signal:

```
const credentials = this.loginModel(); // { email: '...', password: '...' }
```

This keeps your form data in a single reactive container that automatically notifies Angular when values change. The form structure mirrors your data model exactly.

Reactive Forms stores data inside FormControl and FormGroup instances. You access values through the form hierarchy:

```
const credentials = this.loginForm.value; // { email: '...', password: '...' }
```

This separates form state management from your component's data model. The form structure is explicit but requires more setup code.

Template-driven Forms stores data in component properties. You access values directly:

```
const credentials = {email: this.email, password: this.password};
```

This is the most direct approach but requires manually assembling values when you need them. Angular manages form state through directives in the template.

### [How validation works](#how-validation-works)

Each approach defines validation rules differently, affecting where your validation logic lives and how you maintain it.

Signal Forms uses a schema function where you bind validators to field paths:

```
loginForm = form(this.loginModel, (fieldPath) => {
  required(fieldPath.email, {message: 'Email is required'});
  email(fieldPath.email, {message: 'Enter a valid email address'});
});
```

All validation rules live together in one place. The schema function runs once during form creation, and validators execute automatically when field values change. Error messages are part of the validation definition.

Reactive Forms attaches validators when creating controls:

```
loginForm = new FormGroup({
  email: new FormControl('', [Validators.required, Validators.email]),
});
```

Validators are tied to individual controls in the form structure. This distributes validation across your form definition. Error messages typically live in your template.

Template-driven Forms uses directive attributes in the template:

```
<input [(ngModel)]="email" required email />
```

Validation rules live in your template alongside the HTML. This keeps validation close to the UI but spreads logic across template and component.

### [Type safety and autocomplete](#type-safety-and-autocomplete)

TypeScript integration differs significantly between approaches, affecting how much the compiler helps you avoid errors.

Signal Forms infers types from your model structure:

```
const loginModel = signal({email: '', password: ''});
const loginForm = form(loginModel);
// TypeScript knows: loginForm.email exists and returns FieldState<string>
```

You define your data shape once in the signal, and TypeScript automatically knows what fields exist and their types. Accessing `loginForm.username` (which doesn't exist) produces a type error.

Reactive Forms requires explicit type annotations with typed forms:

```
const loginForm = new FormGroup({
  email: new FormControl<string>(''),
  password: new FormControl<string>(''),
});
// TypeScript knows: loginForm.controls.email is FormControl<string>
```

You specify types for each control individually. TypeScript validates your form structure, but you maintain type information separately from your data model.

Template-driven Forms offers minimal type safety:

```
email = '';
password = '';
// TypeScript only knows these are strings, no form-level typing
```

TypeScript understands your component properties but has no knowledge of form structure or validation. You lose compile-time checking for form operations.

## [Choose your approach](#choose-your-approach)

### [Use Signal Forms if:](#use-signal-forms-if)

* You're building new signal-based applications (Angular v22+)
* You want type safety inferred from your model structure
* Schema-based validation appeals to you
* Your team is familiar with signals

### [Use Reactive Forms if:](#use-reactive-forms-if)

* You need production-ready stability
* You're building complex, dynamic forms
* You prefer observable-based patterns
* You need fine-grained control over form state
* You're working on an existing reactive forms codebase

### [Use Template-driven Forms if:](#use-template-driven-forms-if)

* You're building simple forms (login, contact, search)
* You're doing rapid prototyping
* Your form logic is straightforward
* You prefer keeping form logic in templates
* You're working on an existing template-driven codebase

## [Next steps](#next-steps)

To learn more about each approach:

* **Signal Forms**: See the [Overview guide](guide/forms/signals/overview) to get started, or dive into [Form Models](guide/forms/signals/models), [Validation](guide/forms/signals/validation), and [Field State Management](guide/forms/signals/field-state-management)
* **Reactive Forms**: See the [Reactive Forms guide](guide/forms/reactive-forms) in Angular documentation
* **Template-driven Forms**: See the [Template-driven Forms guide](guide/forms/template-driven-forms) in Angular documentation

----
url: https://angular.dev/tutorials/signal-forms/2-connect-form-template
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/1-set-up-form-model)[](/tutorials/signal-forms/3-add-validation)

[](/tutorials/signal-forms/1-set-up-form-model)[](/tutorials/signal-forms/3-add-validation)

Now, you need to connect your form to the template using the `[formField]` directive. This creates two-way data binding between your form model and the input elements.

In this lesson, you'll learn how to:

* Import the [`FormField`](/api/forms/signals/FormField) directive
* Use the `[formField]` directive to bind form fields to inputs
* Connect text inputs and checkboxes to your form
* Display form field values in the template

Let's wire up the template!

***

1. ### [Import the FormField directive](#import-the-formfield-directive)

   Import the [`FormField`](/api/forms/signals/FormField) directive from `@angular/forms/signals` and add it to your component's imports array:

   ```
   import { form, FormField } from '@angular/forms/signals';

   @Component({
     selector: 'app-root',
     templateUrl: './app.html',
     styleUrl: './app.css',
     imports: [FormField],
   })
   ```

2. ### [Bind the email field](#bind-the-email-field)

   In your template, add the `[formField]` directive to the email input:

   ```
   <input type="email" [formField]="loginForm.email" />
   ```

   The `loginForm.email` expression accesses the email field from your form.

3. ### [Bind the password field](#bind-the-password-field)

   Add the `[formField]` directive to the password input:

   ```
   <input type="password" [formField]="loginForm.password" />
   ```

4. ### [Bind the checkbox field](#bind-the-checkbox-field)

   Add the `[formField]` directive to the checkbox input:

   ```
   <input type="checkbox" [formField]="loginForm.rememberMe" />
   ```

5. ### [Display the form values](#display-the-form-values)

   Below the form, there's a debug section to show current form values. Display each field value using `.value()`:

   ```
   <p>Email: {{ loginForm.email().value() }}</p>
   <p>Password: {{ loginForm.password().value() ? '••••••••' : '(empty)' }}</p>
   <p>Remember me: {{ loginForm.rememberMe().value() ? 'Yes' : 'No' }}</p>
   ```

   Form field values are signals, so the displayed values update automatically as you type.

Great work! You've connected your form to the template and displayed the form values. The `[formField]` directive handles two-way data binding automatically - as you type, the `loginModel` signal updates, and the displayed values update immediately.

Next, you'll learn [how to add validation to your form](/tutorials/signal-forms/3-add-validation)!

----
url: https://angular.dev/api/router/NavigationBehaviorOptions
----

```
    
      interface NavigationBehaviorOptions {}
    
    
```

How to handle a navigation request to the current URL.

This value is a subset of the options available in [`OnSameUrlNavigation`](/api/router/OnSameUrlNavigation) and will take precedence over the default value set for the [`Router`](/api/router/Router).

When true, navigates without pushing a new state into history.

```
// Navigate silently to /view
this.router.navigate(['/view'], { skipLocationChange: true });
```

When true, navigates while replacing the current state in history.

```
// Navigate to /view
this.router.navigate(['/view'], { replaceUrl: true });
```

----
url: https://angular.dev/errors/NG05703
----

This error occurs in Server-Side Rendering (SSR) environments when a URL resolution results in an unexpected origin change. This check is a security feature in `@angular/platform-server` designed to protect your application from Server-Side Request Forgery (SSRF) and security bypasses.

## [Description](#description)

To perform HTTP requests and process route states during SSR, Angular must resolve relative URLs to absolute URLs.

During this resolution process, Angular validates that the resolved URL does not unexpectedly point to an external or untrusted origin. If a URL behaves like a relative path but resolves to a different origin, Angular throws `NG05703` and blocks the request/navigation.

### [Common causes](#common-causes)

* **Backslash-prefixed hijack attempts (open redirects / SSRF bypass):** Relative paths beginning with slashes and backslashes (such as `/\\attacker.com/path` or `\\\\attacker.com/path`) can be interpreted differently by browsers and server-side URL parsers. In some parser implementations, these resolve to an absolute URL pointing to `attacker.com`. Angular detects this mismatch and blocks the resolution.
* **Disallowed state changes to different origins:** Navigation or URL updates (e.g. using `location.replaceState` or `location.pushState`) that change the origin of the application when the environment is configured to restrict URL changes to the current origin only.
* **SSR bootstrap URL mismatch:** In server-side rendering, if the `url` passed to the renderer does not align with the base origin configured for the application (such as [`APP_BASE_HREF`](/api/common/APP_BASE_HREF)), the router might attempt to synchronize the location on startup, causing a disallowed origin change.
* **Obfuscated protocol schemes:** Absolute URLs with obfuscated or malformed protocols (e.g. `ht\ntp://evil.com/path`) that attempt to bypass origin checks.

## [Debugging the error](#debugging-the-error)

Check the URL that triggered the error. Ensure it does not contain unexpected backslashes, line breaks, or other characters that could trigger parsing anomalies:

* Avoid using backslashes (`\`) in relative URLs or routing paths.
* Make sure any user-supplied URLs or query parameters are properly validated and sanitized before being processed in SSR.
* **Align SSR bootstrap URL with configured base URL:** If the error occurs on startup in an SSR environment, ensure that the `url` passed to the renderer matches the expected base origin/host (e.g. [`APP_BASE_HREF`](/api/common/APP_BASE_HREF)). Avoid using dynamic URLs from raw request headers (like `X-Forwarded-Host`) if they do not match the trusted application origin.
* If you intentionally want to make requests to a different domain via a protocol-relative URL (`//other-domain.com/path`), ensure it is allowed by the configuration or use an explicit scheme (`http://` or `https://`).

----
url: https://angular.dev/api/common/http/HttpUserEvent
----

```
    
      interface HttpUserEvent<T> {}
    
    
```

## [Description](#description)

A user-defined event.

Grouping all custom events under this type ensures they will be handled and forwarded by all implementations of interceptors.

Jump to details

----
url: https://angular.dev/api/common/testing/MockPlatformLocation
----

```
    
      class MockPlatformLocation implements PlatformLocation {}
    
    
```

@paramconfig`MockPlatformLocationConfig | undefined`

@returns`MockPlatformLocation`

@returns`string`

@paramfn`LocationChangeListener`

@returns`VoidFunction`

@paramfn`LocationChangeListener`

@returns`VoidFunction`

@paramstate`any`

@paramtitle`string`

@paramnewUrl`string`

@returns`void`

@paramstate`any`

@paramtitle`string`

@paramnewUrl`string`

@returns`void`

@returns`void`

@returns`void`

@paramrelativePosition`number`

@returns`void`

@returns`unknown`

Jump to details

----
url: https://angular.dev/api/forms/signals/MIN_NUMBER
----

```
    
      const MIN_NUMBER: LimitKey<number>;
    
    
```

Jump to details

----
url: https://angular.dev/guide/templates/ng-container
----

`<ng-container>` is a special element in Angular that groups multiple elements together or marks a location in a template without rendering a real element in the DOM.

```
<!-- Component template -->
<section>
  <ng-container>
    <h3>User bio</h3>
    <p>Here's some info about the user</p>
  </ng-container>
</section>
```

```
<!-- Rendered DOM -->
<section>
  <h3>User bio</h3>
  <p>Here's some info about the user</p>
</section>
```

You can apply directives to `<ng-container>` to add behaviors or configuration to a part of your template.

Angular ignores all attribute bindings and event listeners applied to `<ng-container>`, including those applied via directive.

## [Using `<ng-container>` to display dynamic contents](#using-ng-container-to-display-dynamic-contents)

`<ng-container>` can act as a placeholder for rendering dynamic content.

### [Rendering components](#rendering-components)

You can use Angular's built-in [`NgComponentOutlet`](/api/common/NgComponentOutlet) directive to dynamically render a component to the location of the `<ng-container>`.

```
@Component({
  template: `
    <h2>Your profile</h2>
    <ng-container [ngComponentOutlet]="profileComponent()" />
  `,
})
export class UserProfile {
  isAdmin = input(false);
  profileComponent = computed(() => (this.isAdmin() ? AdminProfile : BasicUserProfile));
}
```

In the example above, the [`NgComponentOutlet`](/api/common/NgComponentOutlet) directive dynamically renders either `AdminProfile` or `BasicUserProfile` in the location of the `<ng-container>` element.

### [Rendering template fragments](#rendering-template-fragments)

You can use Angular's built-in [`NgTemplateOutlet`](/api/common/NgTemplateOutlet) directive to dynamically render a template fragment to the location of the `<ng-container>`.

```
@Component({
  template: `
    <h2>Your profile</h2>
    <ng-container [ngTemplateOutlet]="profileTemplate()" />

    <ng-template #admin>This is the admin profile</ng-template>
    <ng-template #basic>This is the basic profile</ng-template>
  `,
})
export class UserProfile {
  isAdmin = input(false);
  adminTemplate = viewChild('admin', {read: TemplateRef});
  basicTemplate = viewChild('basic', {read: TemplateRef});
  profileTemplate = computed(() => (this.isAdmin() ? this.adminTemplate() : this.basicTemplate()));
}
```

In the example above, the [`ngTemplateOutlet`](/api/common/NgTemplateOutlet) directive dynamically renders one of two template fragments in the location of the `<ng-container>` element.

For more information regarding [`NgTemplateOutlet`](/api/common/NgTemplateOutlet), see the [NgTemplateOutlet API documentation page](/api/common/NgTemplateOutlet).

## [Using `<ng-container>` with structural directives](#using-ng-container-with-structural-directives)

You can also apply structural directives to `<ng-container>` elements. Common examples of this include [`ngIf`](/api/common/NgIf) and [`ngFor`](/api/common/NgForOf).

```
<ng-container *ngIf="permissions == 'admin'">
  <h1>Admin Dashboard</h1>
  <admin-infographic />
</ng-container>

<ng-container *ngFor="let item of items; index as i; trackBy: trackByFn">
  <h2>{{ item.title }}</h2>
  <p>{{ item.description }}</p>
</ng-container>
```

## [Using `<ng-container>` for injection](#using-ng-container-for-injection)

See the [Dependency Injection guide](/guide/di) for more information on Angular's dependency injection system.

When you apply a directive to `<ng-container>`, descendant elements can inject the directive or anything that the directive provides. Use this when you want to declaratively provide a value to a specific part of your template.

```
@Directive({
  selector: '[theme]',
})
export class Theme {
  // Create an input that accepts 'light' or 'dark`, defaulting to 'light'.
  mode = input<'light' | 'dark'>('light');
}
```

```
<ng-container theme="dark">
  <profile-pic />
  <user-bio />
</ng-container>
```

In the example above, the `ProfilePic` and `UserBio` components can inject the `Theme` directive and apply styles based on its `mode`.

----
url: https://angular.dev/tutorials/signals/11-next-steps
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/10-reacting-to-signal-changes-with-effect)

[](/tutorials/signals/10-reacting-to-signal-changes-with-effect)

You've done it! You completed the Signals tutorial and learned to manage reactive state with signals.

You can also learn more about signals with the following resources:

### [Angular Signals Guide](guide/signals)

[The official guide for Angular Signals.](guide/signals)

[Learn more](guide/signals)

### [Angular's Roadmap](roadmap)

[Read about Angular's open source roadmap including current, future and accomplished projects.](roadmap)

[Learn more](roadmap)

----
url: https://angular.dev/api/forms/AbstractControlDirective
----

```
    
      abstract class AbstractControlDirective {}
    
    
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Base class for control directives.

This class is only used internally in the [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule) and the [`FormsModule`](/api/forms/FormsModule).

Jump to details

----
url: https://angular.dev/tools/libraries/using-libraries
----

When you build your Angular application, take advantage of sophisticated first-party libraries, as well as a rich ecosystem of third-party libraries. [Angular Material](https://material.angular.dev "Angular Material | Angular") is an example of a sophisticated first-party library.

## [Install libraries](#install-libraries)

Libraries are published as [npm packages](reference/configs/npm-packages "Workspace npm dependencies | Angular"), usually together with schematics that integrate them with the Angular CLI. To integrate reusable library code into an application, you need to install the package and import the provided functionality in the location you use it. For most published Angular libraries, use the `ng add <lib_name>` Angular CLI command.

The `ng add` Angular CLI command uses a package manager to install the library package and invokes schematics that are included in the package to other scaffolding within the project code. Examples of package managers include [npm](https://www.npmjs.com "npm") or [yarn](https://yarnpkg.com " Yarn"). Additional scaffolding within the project code includes import statements, fonts, and themes.

A published library typically provides a `README` file or other documentation on how to add that library to your application. For an example, see the [Angular Material](https://material.angular.dev "Angular Material | Angular") documentation.

### [Library typings](#library-typings)

Typically, library packages include typings in `.d.ts` files; see examples in `node_modules/@angular/material`. If the package of your library does not include typings and your IDE complains, you might need to install the `@types/<lib_name>` package with the library.

For example, suppose you have a library named `d3`:

```
npm install d3 --save
npm install @types/d3 --save-dev
```

Types defined in a `@types/` package for a library installed into the workspace are automatically added to the TypeScript configuration for the project that uses that library. TypeScript looks for types in the `node_modules/@types` directory by default, so you do not have to add each type package individually.

If a library does not have typings available at `@types/`, you may use it by manually adding typings for it. To do this:

1. Create a `typings.d.ts` file in your `src/` directory. This file is automatically included as global type definition.

2. Add the following code in `src/typings.d.ts`:

   ```
   declare module 'host' {
     export interface Host {
       protocol?: string;
       hostname?: string;
       pathname?: string;
     }
     export function parse(url: string, queryString?: string): Host;
   }
   ```

3. In the component or file that uses the library, add the following code:

   ```
   import * as host from 'host';
   const parsedUrl = host.parse('https://angular.dev');
   console.log(parsedUrl.hostname);
   ```

Define more typings as needed.

## [Updating libraries](#updating-libraries)

A library is able to be updated by the publisher, and also has individual dependencies which need to be kept current. To check for updates to your installed libraries, use the [`ng update`](cli/update "ng update | CLI |Angular") Angular CLI command.

Use `ng update <lib_name>` Angular CLI command to update individual library versions. The Angular CLI checks the latest published release of the library, and if the latest version is newer than your installed version, downloads it and updates your `package.json` to match the latest version.

When you update Angular to a new version, you need to make sure that any libraries you are using are current. If libraries have interdependencies, you might have to update them in a particular order. See the [Angular Update Guide](/update-guide "Angular Update Guide | Angular") for help.

## [Adding a library to the runtime global scope](#adding-a-library-to-the-runtime-global-scope)

If a legacy JavaScript library is not imported into an application, you may add it to the runtime global scope and load it as if it was added in a script tag. Configure the Angular CLI to do this at build time using the `scripts` and `styles` options of the build target in the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file.

For example, to use the [Bootstrap 4](https://getbootstrap.com/docs/4.0/getting-started/introduction "Introduction | Bootstrap") library

1. Install the library and the associated dependencies using the npm package manager:

   ```
   npm install jquery --save
   npm install popper.js --save
   npm install bootstrap --save
   ```

2. In the `angular.json` configuration file, add the associated script files to the `scripts` array:

   ```
   "scripts": [
       "node_modules/jquery/dist/jquery.slim.js",
       "node_modules/popper.js/dist/umd/popper.js",
       "node_modules/bootstrap/dist/js/bootstrap.js"
     ],
   ```

3. Add the `bootstrap.css` CSS file to the `styles` array:

   ```
   "styles": [
       "node_modules/bootstrap/dist/css/bootstrap.css",
       "src/styles.css"
   ],
   ```

4. Run or restart the `ng serve` Angular CLI command to see Bootstrap 4 work in your application.

### [Using runtime-global libraries inside your app](#using-runtime-global-libraries-inside-your-app)

After you import a library using the "scripts" array, do **not** import it using an import statement in your TypeScript code. The following code snippet is an example import statement.

```
import * as $ from 'jquery';
```

If you import it using import statements, you have two different copies of the library: one imported as a global library, and one imported as a module. This is especially bad for libraries with plugins, like jQuery, because each copy includes different plugins.

Instead, run `npm install @types/jquery` to download typings for your library, add `"jquery"` to the `types` array in `tsconfig.app.json`, and then follow the library installation steps. This gives you access to the global variables exposed by that library.

### [Defining typings for runtime-global libraries](#defining-typings-for-runtime-global-libraries)

If the global library you need to use does not have global typings, you can declare them manually as `any` in `src/typings.d.ts`.

For example:

```
declare var libraryName: any;
```

Some scripts extend other libraries; for instance with jQuery plugins:

```
$('.test').myPlugin();
```

In this case, the installed `@types/jquery` does not include `myPlugin`, so you need to add an interface in `src/typings.d.ts`. For example:

```
interface JQuery {
  myPlugin(options?: any): any;
}
```

If you do not add the interface for the script-defined extension, your IDE shows an error:

```
[TS][Error] Property 'myPlugin' does not exist on type 'JQuery'
```

----
url: https://angular.dev/api/common/http/JsonpInterceptor
----

```
    
      class JsonpInterceptor {}
    
    
```

@paraminjector`EnvironmentInjector`

@returns`JsonpInterceptor`

Identifies and handles a given JSONP request.

@paraminitialRequest`HttpRequest<any>`

The outgoing request object to handle.

@paramnext`HttpHandler`

The next interceptor in the chain, or the backend if no interceptors remain in the chain.

@returns`Observable<HttpEvent<any>>`

An observable of the event stream.

Jump to details

----
url: https://angular.dev/essentials
----

# Essentials

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/overview.md "Edit this page")

A short introduction to some of Angular's main concepts.

## [Interested in Angular?](#interested-in-angular)

Welcome! This *Essentials* guide explains some of Angular's main concepts, helping you understand what it's like to use the framework. This guide focuses on just a few building blocks as a short introduction. If you're looking for deep, comprehensive documentation, you can navigate to specific *In-depth Guides* from the [documentation landing page](overview).

If you prefer to immediately start writing some code, you can [skip straight to the hands-on tutorial](tutorials/learn-angular).

### [Before you start](#before-you-start)

This site expects that you're familiar with HTML, CSS and TypeScript. If you are totally new to web development, you should seek out some more introductory content before coming back here.

In particular, you should be familiar with the following concepts:

* [JavaScript Classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes)
* [TypeScript fundamentals](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html)
* [TypeScript Decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)

## [Next Step](#next-step)

Ready to jump in? It's time to learn about components in Angular!

----
url: https://angular.dev/api/cdk/drag-drop/DragDropModule
----

```
    
      class DragDropModule {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/RouterEvent
----

```
    
      class RouterEvent {}
    
    
```

@paramid`number`

A unique ID that the router assigns to every router navigation.

@paramurl`string`

The URL that is the destination for this navigation.

@returns`RouterEvent`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

## [Description](#description)

Base for events the router goes through, as opposed to events tied to a specific route. Fired one time for any given navigation.

The following code shows how a class subscribes to router events.

```
import {Event, RouterEvent, Router} from '@angular/router';

class MyService {
  constructor(public router: Router) {
    router.events.pipe(
       filter((e: Event | RouterEvent): e is RouterEvent => e instanceof RouterEvent)
    ).subscribe((e: RouterEvent) => {
      // Do something
    });
  }
}
```

Jump to details

----
url: https://angular.dev/api/router/containsTree
----

```
function containsTree(
  container: UrlTree,
  containee: UrlTree,
  options?: Partial<IsActiveMatchOptions> | undefined,
): boolean;
```

@paramcontainer`UrlTree`

The outer or reference [`UrlTree`](/api/router/UrlTree).

@paramcontainee`UrlTree`

The target [`UrlTree`](/api/router/UrlTree) to test against the container.

@paramoptions`Partial<IsActiveMatchOptions> | undefined`

Optional, matching options:

* `paths`: Defines how to compare URL segments ('exact' or 'subset'). Defaults to 'subset'.
* `matrixParams`: Defines how to compare matrix parameters ('exact', 'subset', or 'ignored'). Defaults to 'ignored'.
* `queryParams`: Defines how to compare query parameters ('exact', 'subset', or 'ignored'). Defaults to 'subset'.
* `fragment`: Defines how to compare URL fragments ('exact' or 'ignored'). Defaults to 'ignored'.

@returns`boolean`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/18-forms-validation
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/17-reactive-forms)[](/tutorials/learn-angular/19-creating-an-injectable-service)

[](/tutorials/learn-angular/17-reactive-forms)[](/tutorials/learn-angular/19-creating-an-injectable-service)

Another common scenario when working with forms is the need to validate the inputs to ensure the correct data is submitted.

**NOTE:** Learn more about [validating form input in the in-depth guide](/guide/forms/reactive-forms#validating-form-input).

In this activity, you'll learn how to validate forms with reactive forms.

***

1. ### [Import Validators](#import-validators)

   Angular provides a set of validation tools. To use them, first update the component to import [`Validators`](/api/forms/Validators) from `@angular/forms`.

   ```
   import {ReactiveFormsModule, Validators} from '@angular/forms';

   @Component({...})
   export class App {}
   ```

2. ### [Add validation to form](#add-validation-to-form)

   Every [`FormControl`](/api/forms/FormControl) can be passed the [`Validators`](/api/forms/Validators) you want to use for validating the [`FormControl`](/api/forms/FormControl) values. For example, if you want to make the `name` field in `profileForm` required then use [`Validators.required`](/api/forms/Validators#required). For the `email` field in our Angular form, we want to ensure it's not left empty and follows a valid email address structure. We can achieve this by combining the [`Validators.required`](/api/forms/Validators#required) and [`Validators.email`](/api/forms/Validators#email) validators in an array. Update the `name` and `email` [`FormControl`](/api/forms/FormControl):

   ```
   profileForm = new FormGroup({
     name: new FormControl('', Validators.required),
     email: new FormControl('', [Validators.required, Validators.email]),
   });
   ```

3. ### [Check form validation in template](#check-form-validation-in-template)

   To determine if a form is valid, the [`FormGroup`](/api/forms/FormGroup) class has a `valid` property. You can use this property to dynamically bind attributes. Update the submit `button` to be enabled based on the validity of the form.

   ```
   <button type="submit" [disabled]="!profileForm.valid">Submit</button>
   ```

You now know the basics around how validation works with reactive forms.

Great job learning these core concepts of working with forms in Angular. If you want to learn more, be sure to refer to the [Angular forms documentation](guide/forms/form-validation).

----
url: https://angular.dev/api/core/APP_BOOTSTRAP_LISTENER
----

## [API](/api/core/APP_BOOTSTRAP_LISTENER#api)

```
    
      const APP_BOOTSTRAP_LISTENER: InjectionToken<readonly ((compRef: ComponentRef<any>) => void)[]>;
    
    
```

## [Description](/api/core/APP_BOOTSTRAP_LISTENER#description)

A DI token that provides a set of callbacks to be called for every component that is bootstrapped.

Each callback must take a [`ComponentRef`](/api/core/ComponentRef) instance and return nothing.

`(componentRef: ComponentRef) => void`

Jump to details

----
url: https://angular.dev/api/common/NgPluralCase
----

```
    
      class NgPluralCase {}
    
    
```

@paramvalue`string`

@paramtemplate`TemplateRef<Object>`

@paramviewContainer`ViewContainerRef`

@paramngPlural`NgPlural`

@returns`NgPluralCase`

## [Description](#description)

Creates a view that will be added/removed from the parent [`NgPlural`](/api/common/NgPlural) when the given expression matches the plural expression according to CLDR rules.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

```
<some-element [ngPlural]="value">
  <ng-template ngPluralCase="=0">...</ng-template>
  <ng-template ngPluralCase="other">...</ng-template>
</some-element>
```

See [`NgPlural`](/api/common/NgPlural) for more details and example.

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/2-updating-the-component-class
----

[](/tutorials/learn-angular/1-components-in-angular)[](/tutorials/learn-angular/3-composing-components)

[](/tutorials/learn-angular/1-components-in-angular)[](/tutorials/learn-angular/3-composing-components)

In Angular, the component's logic and behavior are defined in the component's TypeScript class.

**NOTE:** Learn more about [showing dynamic text in the essentials guide](/essentials/templates#showing-dynamic-text).

In this activity, you'll learn how to update the component class and how to use [interpolation](/guide/templates/binding#render-dynamic-text-with-text-interpolation).

***

1. ### [Add a property called `city`](#add-a-property-called-city)

   Update the component class by adding a property called `city` to the `App` class.

   ```
   export class App {
     city = 'San Francisco';
   }
   ```

   The `city` property is of type `string` but you can omit the type because of [type inference in TypeScript](https://www.typescriptlang.org/docs/handbook/type-inference.html). The `city` property can be used in the `App` class and can be referenced in the component template.



   To use a class property in a template, you have to use the `{{ }}` syntax.

2. ### [Update the component template](#update-the-component-template)

   Update the `template` property to match the following HTML:

   ```
   template: `Hello {{ city }}`,
   ```

   This is an example of interpolation and is a part of Angular template syntax. It enables you to do much more than put dynamic text in a template. You can also use this syntax to call functions, write expressions and more.

3. ### [More practice with interpolation](#more-practice-with-interpolation)

   Try this - add another set of `{{ }}` with the contents being `1 + 1`:

   ```
   template: `Hello {{ city }}, {{ 1 + 1 }}`,
   ```

   Angular evaluates the contents of the `{{ }}` and renders the output in the template.

This is just the beginning of what's possible with Angular templates, keep on learning to find out more.

----
url: https://angular.dev/api/common/PercentPipe
----

```
{{ value_expression | percent:digitsInfo:locale }}
```

## [API](#api)

```
    
      class PercentPipe implements PipeTransform {
}
    
    
```

@param\_locale`string`

@returns`PercentPipe`

@paramvalue`string | number`

@paramdigitsInfo`string | undefined`

@paramlocale`string | undefined`

Transforms a number to a percentage string, formatted according to locale rules that determine group sizing and separator, decimal-point character, and other locale-specific configurations.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

The following code shows how the pipe transforms numbers into text strings, according to various format specifications, where the caller's default locale is `en-US`.

```
@Component({
  selector: 'percent-pipe',
  imports: [PercentPipe],
  template: `<div>
    <!--output '26%'-->
    <p>A: {{ a | percent }}</p>

    <!--output '0,134.950%'-->
    <p>B: {{ b | percent: '4.3-5' }}</p>

    <!--output '0 134,950 %'-->
    <p>B: {{ b | percent: '4.3-5' : 'fr' }}</p>
  </div>`,
})
export class PercentPipeComponent {
  a: number = 0.259;
  b: number = 1.3495;
}
```

Jump to details

----
url: https://angular.dev/api/common/formatDate
----

```
function formatDate(
  value: string | number | Date,
  format: string,
  locale: string,
  timezone?: string | undefined,
): string;
```

@paramvalue`string | number | Date`

The date to format, as a Date, or a number (milliseconds since UTC epoch) or an [ISO date-time string](https://www.w3.org/TR/NOTE-datetime).

@paramformat`string`

The date-time components to include. See [`DatePipe`](/api/common/DatePipe) for details.

@paramlocale`string`

A locale code for the locale format rules to use.

@paramtimezone`string | undefined`

The time zone. A time zone offset from GMT (such as `'+0430'`). If not specified, uses host system settings.

@returns`string`

The formatted date string.

## [Description](#description)

Formats a date according to locale rules.

## [Exported by](#exported-by)

* `CommonModule`

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/DragDropConfig
----

```
    
      interface DragDropConfig extends Partial<DragRefConfig> {}
    
    
```

Minimum amount of pixels that the user should drag, before the CDK initiates a drag sequence.

Amount the pixels the user should drag before the CDK considers them to have changed the drag direction.

Ref that the current drag item is nested in.

Jump to details

----
url: https://angular.dev/api/common/FormatWidth
----

Date locale data getters are deprecated

## [API](#api)

```
    
      enum FormatWidth {}
    
    
```

For `en-US`, `'M/d/yy, h:mm a'` (Example: `6/15/15, 9:03 AM`)

For `en-US`, `'MMM d, y, h:mm:ss a'` (Example: `Jun 15, 2015, 9:03:01 AM`)

For `en-US`, `'MMMM d, y, h:mm:ss a z'` (Example: `June 15, 2015 at 9:03:01 AM GMT+1`)

For `en-US`, `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (Example: `Monday, June 15, 2015 at 9:03:01 AM GMT+01:00`)

Jump to details

----
url: https://angular.dev/api/core/REQUEST_CONTEXT
----

```
    
      const REQUEST_CONTEXT: InjectionToken<unknown>;
    
    
```

## [Description](#description)

Injection token for additional request context.

Use this token to pass custom metadata or context related to the current request in server-side rendering.

## [Usage Notes](#usage-notes)

This token is only available during server-side rendering and will be `null` in other contexts.

Jump to details

----
url: https://angular.dev/api/core/SkipSelf
----

```
@SkipSelf();
```

## [Usage Notes](#usage-notes)

In the following example, the dependency can be resolved when instantiating a child, but not when instantiating the class itself.

```
class Dependency {}

        @Injectable()
        class NeedsDependency {
          constructor(@SkipSelf() public dependency: Dependency) {}
        }

        const parent = Injector.create({providers: [{provide: Dependency, deps: []}]});
        const child = Injector.create({
          providers: [{provide: NeedsDependency, deps: [Dependency]}],
          parent,
        });
        expect(child.get(NeedsDependency).dependency instanceof Dependency).toBe(true);

        const inj = Injector.create({
          providers: [{provide: NeedsDependency, deps: [[new Self(), Dependency]]}],
        });
        expect(() => inj.get(NeedsDependency)).toThrowError();
```

Jump to details

----
url: https://angular.dev/errors/NG01002
----

This error occurs when you call `setValue` on a [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray) but the value you pass is missing an entry for one of the registered controls.

`setValue` is strict — it expects a value for every control. If you want to update only some controls, use `patchValue` instead.

## [Debugging the error](#debugging-the-error)

Check which control is named in the error message, then make sure your value object includes it.

A common source of this error is spreading an object that doesn't have all the keys:

```
const someValue = {first: 'Nancy'}; // 'last' is missing

form.setValue({...someValue}); // throws NG01002
```

This can happen if `someValue` comes from an API response, a partial state update, or a type that doesn't fully match the form structure. In those cases, either fill in the missing keys explicitly or switch to `patchValue`.

### [FormGroup](#formgroup)

```
const form = new FormGroup({
  first: new FormControl(''),
  last: new FormControl(''),
});

// 'last' is not in the value — throws NG01002
form.setValue({first: 'Nancy'});

// both controls are covered — works fine
form.setValue({first: 'Nancy', last: 'Drew'});
```

### [FormArray](#formarray)

```
const formArray = new FormArray([new FormControl(''), new FormControl('')]);

// only one value for two controls — throws NG01002
formArray.setValue(['Nancy']);

// one value per control — works fine
formArray.setValue(['Nancy', 'Drew']);
```

----
url: https://angular.dev/api/core/provideEnvironmentInitializer
----

```
function provideEnvironmentInitializer(
  initializerFn: () => void,
): EnvironmentProviders;
```

@paraminitializerFn`() => void`

@returns`EnvironmentProviders`

## [Description](#description)

This function is used to provide initialization functions that will be executed upon construction of an environment injector.

Note that the provided initializer is run in the injection context.

Previously, this was achieved using the [`ENVIRONMENT_INITIALIZER`](/api/core/ENVIRONMENT_INITIALIZER) token which is now deprecated.

## [Usage Notes](#usage-notes)

The following example illustrates how to configure an initialization function using [`provideEnvironmentInitializer()`](/api/core/provideEnvironmentInitializer)

```
createEnvironmentInjector(
  [
    provideEnvironmentInitializer(() => {
      console.log('environment initialized');
    }),
  ],
  parentInjector
);
```

Jump to details

----
url: https://angular.dev/api/platform-server/PlatformConfig
----

```
    
      interface PlatformConfig {}
    
    
```

The initial DOM to use to bootstrap the server application. @default create a new DOM using Domino

The URL for the current application state. This is used for initializing the platform's location. `protocol`, `hostname`, and `port`.

Jump to details

----
url: https://angular.dev/api/router/withDebugTracing
----

```
function withDebugTracing(): DebugTracingFeature;
```

@returns`DebugTracingFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can enable debug tracing:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withDebugTracing())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/api/ssr/ServerRoutePrerenderWithParams
----

```
    
      interface ServerRoutePrerenderWithParams extends Omit<ServerRoutePrerender, 'fallback'> {}
    
    
```

Optional strategy to use if the SSG path is not pre-rendered. This is especially relevant for routes with parameterized URLs, where some paths may not be pre-rendered at build time.

This property determines how to handle requests for paths that are not pre-rendered:

* [`PrerenderFallback.Server`](/api/ssr/PrerenderFallback#Server): Use Server-Side Rendering (SSR) to dynamically generate the page at request time.
* [`PrerenderFallback.Client`](/api/ssr/PrerenderFallback#Client): Use Client-Side Rendering (CSR) to fetch and render the page on the client side.
* [`PrerenderFallback.None`](/api/ssr/PrerenderFallback#None): No fallback; if the path is not pre-rendered, the server will not handle the request.

@default [`PrerenderFallback.Server`](/api/ssr/PrerenderFallback#Server) if not provided.

A function that returns a Promise resolving to an array of objects, each representing a route path with URL parameters. This function runs in the injector context, allowing access to Angular services and dependencies.

It also works for catch-all routes (e.g., `/**`), where the parameter name will be `**` and the return value will be the segments of the path, such as `/foo/bar`. These routes can also be combined, e.g., `/product/:id/**`, where both a parameterized segment (`:id`) and a catch-all segment (`**`) can be used together to handle more complex paths.

The path associated with this route.

Optional additional headers to include in the response for this route.

Specifies that the route uses Static Site Generation (SSG) mode.

Jump to details

----
url: https://angular.dev/api/animations/AnimationOptions
----

```
    
      interface AnimationOptions {}
    
    
```

Sets a time-delay for initiating an animation action. A number and optional time unit, such as "1s" or "10ms" for one second and 10 milliseconds, respectively.The default unit is milliseconds. Default value is 0, meaning no delay.

A set of developer-defined parameters that modify styling and timing when an animation action starts. An array of key-value pairs, where the provided value is used as a default.

## [Description](#description)

Options that control animation styling and timing.

The following animation functions accept [`AnimationOptions`](/api/animations/AnimationOptions) data:

* [`transition()`](/api/animations/transition)
* [`sequence()`](/api/animations/sequence)
* `group()`
* [`query()`](/api/animations/query)
* [`animation()`](/api/animations/animation)
* [`useAnimation()`](/api/animations/useAnimation)
* [`animateChild()`](/api/animations/animateChild)

Programmatic animations built using the [`AnimationBuilder`](/api/animations/AnimationBuilder) service also make use of [`AnimationOptions`](/api/animations/AnimationOptions).

Jump to details

----
url: https://angular.dev/api/common/ImagePlaceholderConfig
----

```
    
      interface ImagePlaceholderConfig {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ViewContainerRef
----

```
    
      abstract class ViewContainerRef {
}
    
    
```

Anchor element that specifies the location of this container in the containing view. Each view container can have only one anchor element, and each anchor element can have only a single view container.

Root elements of views attached to this container become siblings of the anchor element in the rendered view.

Access the [`ViewContainerRef`](/api/core/ViewContainerRef) of an element by placing a [`Directive`](/api/core/Directive) injected with [`ViewContainerRef`](/api/core/ViewContainerRef) on the element, or use a [`ViewChild`](/api/core/ViewChild) query.

The dependency injector for this view container.

@deprecated

No replacement

Destroys all views in this container.

@returns`void`

Retrieves a view from this container.

@paramindex`number`

The 0-based index of the view to retrieve.

@returns`ViewRef | null`

The [`ViewRef`](/api/core/ViewRef) instance, or null if the index is out of range.

Reports how many views are currently attached to this container.

Instantiates an embedded view and inserts it into this container.

@paramtemplateRef`TemplateRef<C>`

The HTML template that defines the view.

@paramcontext`C | undefined`

The data-binding context of the embedded view, as declared in the `<ng-template>` usage.

@paramoptions`{ index?: number | undefined; injector?: Injector | undefined; onError?: ((error: Error, details: ErrorDetails) => void) | undefined; } | undefined`

Extra configuration for the created view. Includes:

* index: The 0-based index at which to insert the new view into this container. If not specified, appends the new view as the last entry.
* injector: Injector to be used within the embedded view.
* onError: A callback that will be invoked when an error occurs during the view's change detection or lifecycle hooks. Note that this will not catch errors thrown during the view's creation.

@returns`EmbeddedViewRef<C>`

The [`ViewRef`](/api/core/ViewRef) instance for the newly created view.

Instantiates an embedded view and inserts it into this container.

@paramtemplateRef`TemplateRef<C>`

The HTML template that defines the view.

@paramcontext`C | undefined`

The data-binding context of the embedded view, as declared in the `<ng-template>` usage.

@paramindex`number | undefined`

The 0-based index at which to insert the new view into this container. If not specified, appends the new view as the last entry.

@returns`EmbeddedViewRef<C>`

The [`ViewRef`](/api/core/ViewRef) instance for the newly created view.

Instantiates a component and inserts its host view into this view container.

@paramcomponentType`Type<C>`

Component Type to use.

@paramoptions`{ index?: number | undefined; injector?: Injector | undefined; ngModuleRef?: NgModuleRef<unknown> | undefined; environmentInjector?: EnvironmentInjector | NgModuleRef<unknown> | undefined; projectableNodes?: Node[][] | undefined; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[] | undefined; bindings?: Binding[] | undefined; onError?: ((error: Error, context: ErrorDetails) => void) | undefined; } | undefined`

An object that contains extra parameters:

* index: the index at which to insert the new component's host view into this container. If not specified, appends the new view as the last entry.
* injector: the injector to use as the parent for the new component.
* ngModuleRef: an NgModuleRef of the component's NgModule, you should almost always provide this to ensure that all expected providers are available for the component instantiation.
* environmentInjector: an EnvironmentInjector which will provide the component's environment. you should almost always provide this to ensure that all expected providers are available for the component instantiation. This option is intended to replace the `ngModuleRef` parameter.
* projectableNodes: list of DOM nodes that should be projected through [`<ng-content>`](api/core/ng-content) of the new component instance.
* directives: Directives that should be applied to the component.
* bindings: Bindings that should be applied to the component.
* onError: A callback that will be invoked when an error occurs during the component's change detection or lifecycle hooks. Note that this will not catch errors thrown during the component's construction.

@returns`ComponentRef<C>`

The new [`ComponentRef`](/api/core/ComponentRef) which contains the component instance and the host view.

Inserts a view into this container.

@paramviewRef`ViewRef`

The view to insert.

@paramindex`number | undefined`

The 0-based index at which to insert the view. If not specified, appends the new view as the last entry.

@returns`ViewRef`

The inserted [`ViewRef`](/api/core/ViewRef) instance.

Moves a view to a new location in this container.

@paramviewRef`ViewRef`

The view to move.

@paramcurrentIndex`number`

The 0-based index of the new location.

@returns`ViewRef`

The moved [`ViewRef`](/api/core/ViewRef) instance.

Returns the index of a view within the current container.

@paramviewRef`ViewRef`

The view to query.

@returns`number`

The 0-based index of the view's position in this container, or `-1` if this container doesn't contain the view.

Destroys a view attached to this container

@paramindex`number | undefined`

The 0-based index of the view to destroy. If not specified, the last view in the container is removed.

@returns`void`

Detaches a view from this container without destroying it. Use along with `insert()` to move a view within the current container.

@paramindex`number | undefined`

The 0-based index of the view to detach. If not specified, the last view in the container is detached.

@returns`ViewRef | null`

## [Description](#description)

Represents a container where one or more views can be attached to a component.

Can contain *host views* (created by instantiating a component with the [`createComponent()`](/api/core/createComponent) method), and *embedded views* (created by instantiating a [`TemplateRef`](/api/core/TemplateRef) with the `createEmbeddedView()` method).

A view container instance can contain other view containers, creating a view hierarchy.

## [Usage Notes](#usage-notes)

The example below demonstrates how the [`createComponent`](/api/core/createComponent) function can be used to create an instance of a ComponentRef dynamically and attach it to an ApplicationRef, so that it gets included into change detection cycles.

Note: the example uses standalone components, but the function can also be used for non-standalone components (declared in an NgModule) as well.

```
@Component({
  selector: 'dynamic',
  template: `<span>This is a content of a dynamic component.</span>`,
})
class DynamicComponent {
  vcr = inject(ViewContainerRef);
}

@Component({
  selector: 'app',
  template: `<main>Hi! This is the main content.</main>`,
})
class AppComponent {
  vcr = inject(ViewContainerRef);

  ngAfterViewInit() {
    const compRef = this.vcr.createComponent(DynamicComponent);
    compRef.changeDetectorRef.detectChanges();
  }
}
```

Jump to details

----
url: https://angular.dev/api/ssr/ServerRoute
----

```
    
      type ServerRoute = ServerRouteClient | ServerRoutePrerender | ServerRoutePrerenderWithParams | ServerRouteServer
    
    
```

Jump to details

----
url: https://angular.dev/api/core/effect
----

```
function effect(
  effectFn: (onCleanup: EffectCleanupRegisterFn) => void,
  options?: CreateEffectOptions | undefined,
): EffectRef;
```

@parameffectFn`(onCleanup: EffectCleanupRegisterFn) => void`

@paramoptions`CreateEffectOptions | undefined`

@returns`EffectRef`

## [Description](#description)

Registers an "effect" that will be scheduled & executed whenever the signals that it reads changes.

Angular has two different kinds of effect: component effects and root effects. Component effects are created when [`effect()`](/api/core/effect) is called from a component, directive, or within a service of a component/directive. Root effects are created when [`effect()`](/api/core/effect) is called from outside the component tree, such as in a root service.

The two effect types differ in their timing. Component effects run as a component lifecycle event during Angular's synchronization (change detection) process, and can safely read input signals or create/destroy views that depend on component state. Root effects run as microtasks and have no connection to the component tree or change detection.

[`effect()`](/api/core/effect) must be run in injection context, unless the `injector` option is manually specified.

Jump to details

----
url: https://angular.dev/api/cdk/testing/manualChangeDetection
----

```
function manualChangeDetection<T>(fn: () => Promise<T>): Promise<T>;
```

@paramfn`() => Promise<T>`

The function to disable auto change detection for.

@returns`Promise<T>`

Jump to details

----
url: https://angular.dev/api/forms/signals/minLength
----

```
function minLength<
  TValue extends ValueWithLengthOrSize,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  minLength: number | LogicFn<TValue, number | undefined, TPathKind>,
  config?: BaseValidatorConfig<TValue, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@paramminLength`number | LogicFn<TValue, number | undefined, TPathKind>`

The minimum length, or a LogicFn that returns the minimum length.

@paramconfig`BaseValidatorConfig<TValue, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`minLengthError()`](/api/forms/signals/minLengthError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

Jump to details

----
url: https://angular.dev/errors/NG0300
----

[](https://www.youtube.com/watch?v=z_3Z5mOm59I\&autoplay=1)

Two or more [components](guide/components) use the same element selector. Because there can only be a single component associated with an element, selectors must be unique strings to prevent ambiguity for Angular.

This error occurs at runtime when you apply two selectors to a single node, each of which matches a different component, or when you apply a single selector to a node and it matches more than one component.

```
import {Component} from '@angular/core';

@Component({
  selector: '[stroked-button]',
  templateUrl: './stroked-button.html',
})
export class StrokedBtn {}

@Component({
  selector: '[raised-button]',
  templateUrl: './raised-button.html',
})
export class RaisedBtn {}

@Component({
  selector: 'app-root',
  template: `
    <!-- This node has 2 selectors: stroked-button and raised-button, and both match a different component: StrokedBtnComponent, and RaisedBtnComponent , so NG0300 will be raised  -->

    <button stroked-button raised-button></button>
  `,
})
export class App {}
```

## [Debugging the error](#debugging-the-error)

Use the element name from the error message to search for places where you're using the same selector declaration in your codebase:

```
@Component({
  selector: 'YOUR_STRING',
…
})
```

Ensure that each component has a unique CSS selector. This will guarantee that Angular renders the component you expect.

If you're having trouble finding multiple components with this selector tag name, check for components from imported component libraries, such as Angular Material. Make sure you're following the [best practices](style-guide#choosing-component-selectors) for your selectors to prevent collisions.

----
url: https://angular.dev/api/core/createNgModule
----

```
function createNgModule<T>(
  ngModule: Type<T>,
  parentInjector?: Injector | undefined,
): NgModuleRef<T>;
```

@paramngModule`Type<T>`

NgModule class.

@paramparentInjector`Injector | undefined`

Optional injector instance to use as a parent for the module injector. If not provided, `NullInjector` will be used instead.

@returns`NgModuleRef<T>`

NgModuleRef that represents an NgModule instance.

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/1-components-in-angular
----

[](/tutorials/learn-angular/2-updating-the-component-class)

[](/tutorials/learn-angular/2-updating-the-component-class)

Components are the foundational building blocks for any Angular application. Each component has three parts:

* TypeScript class
* HTML template
* CSS styles

**NOTE:** Learn more about [components in the essentials guide](/essentials/components).

In this activity, you'll learn how to update the template and styles of a component.

***

This is a great opportunity for you to get started with Angular.

1. ### [Update the component template](#update-the-component-template)

   Update the `template` property to read `Hello Universe`

   ```
   template: `
     Hello Universe
   `,
   ```

   When you changed the HTML template, the preview updated with your message. Let's go one step further: change the color of the text.

2. ### [Update the component styles](#update-the-component-styles)

   Update the styles value and change the `color` property from `blue` to `#a144eb`.

   ```
   styles: `
     :host {
       color: #a144eb;
     }
   `,
   ```

   When you check the preview, you'll find that the text color will be changed.

In Angular, you can use all the browser supported CSS and HTML that's available. If you'd like, you can store your template and styles in separate files.

----
url: https://angular.dev/api/forms/AsyncValidatorFn
----

```
    
      interface AsyncValidatorFn {  (control: AbstractControl<any, any, any>): Promise<ValidationErrors | null> | Observable<ValidationErrors | null>;}
    
    
```

@paramcontrol`AbstractControl<any, any, any>`

@returns`Promise<ValidationErrors | null> | Observable<ValidationErrors | null>`

Jump to details

----
url: https://angular.dev/api/animations/AnimationBuilder
----

```
    
      abstract class AnimationBuilder {}
    
    
```

Builds a factory for producing a defined animation.

@paramanimation`AnimationMetadata | AnimationMetadata[]`

A reusable animation definition.

@returns`AnimationFactory`

A factory object that can create a player for the defined animation.

## [Usage Notes](#usage-notes)

To use this service, add it to your component or directive as a dependency. The service is instantiated along with your component.

Apps do not typically need to create their own animation players, but if you do need to, follow these steps:

1. Use the `AnimationBuilder.build()` method to create a programmatic animation. The method returns an [`AnimationFactory`](/api/animations/AnimationFactory) instance.

2. Use the factory object to create an [`AnimationPlayer`](/api/animations/AnimationPlayer) and attach it to a DOM element.

3. Use the player object to control the animation programmatically.

For example:

```
// import the service from BrowserAnimationsModule
import {AnimationBuilder} from '@angular/animations';
// require the service as a dependency
class MyCmp {
  constructor(private _builder: AnimationBuilder) {}

  makeAnimation(element: any) {
    // first define a reusable animation
    const myAnimation = this._builder.build([
      style({ width: 0 }),
      animate(1000, style({ width: '100px' }))
    ]);

    // use the returned factory object to create a player
    const player = myAnimation.create(element);

    player.play();
  }
}
```

Jump to details

----
url: https://angular.dev/tutorials/signals/2-deriving-state-with-computed-signals
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/1-creating-your-first-signal)[](/tutorials/signals/3-deriving-state-with-linked-signals)

[](/tutorials/signals/1-creating-your-first-signal)[](/tutorials/signals/3-deriving-state-with-linked-signals)

Now that you've learned [how to create and update signals](/tutorials/signals/1-creating-your-first-signal), let's learn about computed signals. Computed signals are derived values that automatically update when their dependencies change. They're perfect for creating reactive calculations based on other signals.

In this activity, you'll learn how to use the [`computed()`](/api/core/computed) function to create derived state that updates automatically when the underlying signals change.

Let's enhance our user status system by adding computed values that derive information from our user status signal. The starter code now includes three status options: `'online'`, `'away'`, and `'offline'`.

***

1. ### [Import computed function](#import-computed-function)

   Add [`computed`](/api/core/computed) to your existing imports.

   ```
   // Add computed to existing imports
   import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/core';
   ```

2. ### [Create a computed signal for notifications](#create-a-computed-signal-for-notifications)

   Add a computed signal that determines if notifications should be enabled based on user status.

   ```
   notificationsEnabled = computed(() => this.userStatus() === 'online');
   ```

   This computed signal will automatically recalculate whenever the `userStatus` signal changes. Notice how we call `this.userStatus()` inside the computed function to read the signal's value.

3. ### [Create a computed signal for a descriptive message](#create-a-computed-signal-for-a-descriptive-message)

   Add a computed signal that creates a descriptive message based on the user status.

   ```
   statusMessage = computed(() => {
     const status = this.userStatus();
     switch (status) {
       case 'online':
         return 'Available for meetings and messages';
       case 'away':
         return 'Temporarily away, will respond soon';
       case 'offline':
         return 'Not available, check back later';
       default:
         return 'Status unknown';
     }
   });
   ```

   This shows how computed signals can handle more complex logic with switch statements and string transformations.

4. ### [Create a computed signal that calculates working hours availability](#create-a-computed-signal-that-calculates-working-hours-availability)

   Add a computed signal that calculates if the user is within their working hours.

   ```
   isWithinWorkingHours = computed(() => {
     const now = new Date();
     const hour = now.getHours();
     const isWeekday = now.getDay() > 0 && now.getDay() < 6;
     return this.userStatus() !== 'offline' && isWeekday && hour >= 9 && hour < 17;
   });
   ```

   This demonstrates how computed signals can perform calculations and combine multiple data sources. The value updates automatically when the `userStatus` changes.

5. ### [Display the computed values in the template](#display-the-computed-values-in-the-template)

   The template already has placeholders showing "Loading...". Replace them with your computed signals:

   1. For notifications, replace `Loading...` with an `@if` block:

      ```
      @if (notificationsEnabled()) {
        Enabled
      } @else {
        Disabled
      }
      ```

   2. For the message, replace `Loading...` with:

      ```
      {{ statusMessage() }}
      ```

   3. For working hours, replace `Loading...` with an `@if` block:

      ```
      @if (isWithinWorkingHours()) {
        Yes
      } @else {
        No
      }
      ```

   Notice how computed signals are called just like regular signals - with parentheses!

Excellent! You've now learned how to create computed signals.

Here are some key points to remember:

* **Computed signals are reactive**: They automatically update when their dependencies change
* **They're read-only**: You can't directly set computed values, they're derived from other signals
* **They can contain complex logic**: Use them for calculations, transformations, and derived state
* **They provide a way to make performant computations based on dynamic state**: Angular only recalculates them when their dependencies actually change

In the next lesson, you'll learn about [a different way to derive state with linkedSignals](/tutorials/signals/3-deriving-state-with-linked-signals)!

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragEnd
----

```
    
      interface CdkDragEnd<T = any> {}
    
    
```

Draggable that emitted the event.

Distance in pixels that the user has dragged since the drag sequence started.

Position where the pointer was when the item was dropped

Native event that caused the dragging to stop.

Jump to details

----
url: https://angular.dev/api/core/WritableResource
----

```
    
      interface WritableResource<T> extends Resource<T> {
}
    
    
```

@paramthis`T extends undefined ? this : never`

@returns`this is WritableResource<Exclude<T, undefined>>`

@returns`boolean`

## [Description](#description)

A [`Resource`](/api/core/Resource) with a mutable value.

Overwriting the value of a resource sets it to the 'local' state.

Jump to details

----
url: https://angular.dev/api/forms/signals/FieldTree
----

```
    
      type FieldTree<TModel, TKey extends string | number = string | number, TMode extends 'writable' | 'readonly' = 'writable'> = (() => [TModel] extends [AbstractControl]    ? CompatFieldState<TModel, TKey, TMode>    : FieldStateByMode<TModel, TKey, TMode>) &    // Children:    (TModel extends AbstractControl      ? object      : TModel extends ReadonlyArray<infer U>        ? ReadonlyArrayLike<MaybeFieldTree<U, number, TMode>>        : TModel extends Record<string, any>          ? Subfields<TModel, TMode>          : object)
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/SchemaFn
----

```
    
      type SchemaFn<TModel, TPathKind extends PathKind = PathKind.Root> = (  p: SchemaPathTree<TModel, TPathKind>,) => void
    
    
```

## [Description](#description)

A function that receives a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) and applies rules to fields.

A [`SchemaFn`](/api/forms/signals/SchemaFn) can be passed directly to [`form`](/api/forms/signals/form) or to the [`schema`](/api/forms/signals/schema) function to create a cached [`Schema`](/api/forms/signals/Schema).

```
const userFormSchema: SchemaFn<User> = (p) => {
  required(p.name);
  disabled(p.email, {when: ({valueOf}) => valueOf(p.name) === ''});
};

const f = form(userModel, userFormSchema, {injector});
```

Jump to details

----
url: https://angular.dev/api/platform-browser/provideClientHydration
----

```
function provideClientHydration(
  ...features: HydrationFeature<HydrationFeatureKind>[]
): EnvironmentProviders;
```

@paramfeatures`HydrationFeature<HydrationFeatureKind>[]`

Optional features to configure additional hydration behaviors.

@returns`EnvironmentProviders`

A set of providers to enable hydration.

## [Description](#description)

Sets up providers necessary to enable hydration functionality for the application.

By default, the function enables the recommended set of features for the optimal performance for most of the applications. It includes the following features:

* Reconciling DOM hydration. Learn more about it [here](guide/hydration).
* [`HttpClient`](api/common/http/HttpClient) response caching while running on the server and transferring this cache to the client to avoid extra HTTP requests. Learn more about data caching [here](guide/ssr#caching-data-when-using-httpclient). Incremental hydration. [Learn more](guide/incremental-hydration).

These functions allow you to disable some of the default features or enable new ones:

* [`withNoHttpTransferCache`](/api/platform-browser/withNoHttpTransferCache) to disable HTTP transfer cache
* [`withHttpTransferCacheOptions`](/api/platform-browser/withHttpTransferCacheOptions) to configure some HTTP transfer cache options
* [`withI18nSupport`](/api/platform-browser/withI18nSupport) to enable hydration support for i18n blocks
* [`withEventReplay`](/api/platform-browser/withEventReplay) to enable support for replaying user events
* [`withNoIncrementalHydration`](/api/platform-browser/withNoIncrementalHydration) to disable incremental hydration

## [Usage Notes](#usage-notes)

Basic example of how you can enable hydration in your application when [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) function is used:

```
bootstrapApplication(App, {
  providers: [provideClientHydration()]
});
```

Alternatively if you are using NgModules, you would add [`provideClientHydration`](/api/platform-browser/provideClientHydration) to your root app module's provider list.

```
@NgModule({
  declarations: [RootCmp],
  bootstrap: [RootCmp],
  providers: [provideClientHydration()],
})
export class AppModule {}
```

Jump to details

----
url: https://angular.dev/api/upgrade/VERSION
----

* * [VERSION](/api/upgrade/VERSION)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](#api)

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/tools/cli/aot-compiler
----

An Angular application consists mainly of components and their HTML templates. Because the components and templates provided by Angular cannot be understood by the browser directly, Angular applications require a compilation process before they can run in a browser.

The Angular ahead-of-time (AOT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code during the build phase *before* the browser downloads and runs that code. Compiling your application during the build process provides a faster rendering in the browser.

This guide explains how to specify metadata and apply available compiler options to compile your applications efficiently using the AOT compiler.

**HELPFUL:** [Watch Alex Rickabaugh explain the Angular compiler](https://www.youtube.com/watch?v=anphffaCZrQ) at AngularConnect 2019.

Here are some reasons you might want to use AOT.

| Reasons                                 | Details                                                                                                                                                                                                                                            |
| :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Faster rendering                        | With AOT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the application first.                                       |
| Fewer asynchronous requests             | The compiler *inlines* external HTML templates and CSS style sheets within the application JavaScript, eliminating separate ajax requests for those source files.                                                                                  |
| Smaller Angular framework download size | There's no need to download the Angular compiler if the application is already compiled. The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.                                              |
| Detect template errors earlier          | The AOT compiler detects and reports template binding errors during the build step before users can see them.                                                                                                                                      |
| Better security                         | AOT compiles HTML templates and components into JavaScript files long before they are served to the client. With no templates to read and no risky client-side HTML or JavaScript evaluation, there are fewer opportunities for injection attacks. |

## [Choosing a compiler](#choosing-a-compiler)

Angular offers two ways to compile your application:

| Angular compile     | Details                                                                                           |
| :------------------ | :------------------------------------------------------------------------------------------------ |
| Just-in-Time (JIT)  | Compiles your application in the browser at runtime. This was the default until Angular 8.        |
| Ahead-of-Time (AOT) | Compiles your application and libraries at build time. This is the default starting in Angular 9. |

When you run the [`ng build`](cli/build) (build only) or [`ng serve`](cli/serve) (build and serve locally) CLI commands, the type of compilation (JIT or AOT) depends on the value of the `aot` property in your build configuration specified in `angular.json`. By default, `aot` is set to `true` for new CLI applications.

See the [CLI command reference](cli) and [Building and serving Angular apps](tools/cli/build) for more information.

## [How AOT works](#how-aot-works)

The Angular AOT compiler extracts **metadata** to interpret the parts of the application that Angular is supposed to manage. You can specify the metadata explicitly in **decorators** such as [`@Component()`](/api/core/Component), or implicitly in the constructor declarations of the decorated classes. The metadata tells Angular how to construct instances of your application classes and interact with them at runtime.

In the following example, the [`@Component()`](/api/core/Component) metadata object and the class constructor tell Angular how to create and display an instance of `Typical`.

```
@Component({
  selector: 'app-typical',
  template: '<div>A typical component for {{data().name}}</div>',
})
export class Typical {
  data = input.required<TypicalData>();
  private someService = inject(SomeService);
}
```

The Angular compiler extracts the metadata *once* and generates a *factory* for `Typical`. When it needs to create a `Typical` instance, Angular calls the factory, which produces a new visual element, bound to a new instance of the component class with its injected dependency.

### [Compilation phases](#compilation-phases)

There are three phases of AOT compilation.

|    | Phase                  | Details                                                                                                                                                                                                                                                                                                                     |
| :- | :--------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1  | code analysis          | In this phase, the TypeScript compiler and *AOT collector* create a representation of the source. The collector does not attempt to interpret the metadata it collects. It represents the metadata as best it can and records errors when it detects a metadata syntax violation.                                           |
| 2  | code generation        | In this phase, the compiler's `StaticReflector` interprets the metadata collected in phase 1, performs additional validation of the metadata, and throws an error if it detects a metadata restriction violation.                                                                                                           |
| 3  | template type checking | In this phase, the Angular *template compiler* uses the TypeScript compiler to validate the binding expressions in templates. Strict checking in this phase is enabled by default and controlled by the `strictTemplates` configuration option; see [Angular compiler options](reference/configs/angular-compiler-options). |

### [Metadata restrictions](#metadata-restrictions)

You write metadata in a *subset* of TypeScript that must conform to the following general constraints:

* Limit [expression syntax](#expression-syntax-limitations) to the supported subset of JavaScript
* Only reference exported symbols after [code folding](#code-folding)
* Only call [functions supported](#supported-classes-and-functions) by the compiler
* Input/Outputs and data-bound class members must be public or protected.For additional guidelines and instructions on preparing an application for AOT compilation, see [Angular: Writing AOT-friendly applications](https://medium.com/sparkles-blog/angular-writing-aot-friendly-applications-7b64c8afbe3f).

**HELPFUL:** Errors in AOT compilation commonly occur because of metadata that does not conform to the compiler's requirements (as described more fully below). For help in understanding and resolving these problems, see [AOT Metadata Errors](tools/cli/aot-metadata-errors).

### [Configuring AOT compilation](#configuring-aot-compilation)

You can provide options in the [TypeScript configuration file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) that controls the compilation process. See [Angular compiler options](reference/configs/angular-compiler-options) for a complete list of available options.

## [Phase 1: Code analysis](#phase-1-code-analysis)

The TypeScript compiler does some of the analytic work of the first phase. It emits the `.d.ts` *type definition files* with type information that the AOT compiler needs to generate application code. At the same time, the AOT **collector** analyzes the metadata recorded in the Angular decorators and outputs metadata information in **`.metadata.json`** files, one per `.d.ts` file.

You can think of `.metadata.json` as a diagram of the overall structure of a decorator's metadata, represented as an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree).

**HELPFUL:** Angular's [schema.ts](https://github.com/angular/angular/blob/12.2.x/packages/compiler-cli/src/metadata/schema.ts) describes the JSON format as a collection of TypeScript interfaces.

### [Expression syntax limitations](#expression-syntax-limitations)

The AOT collector only understands a subset of JavaScript. Define metadata objects with the following limited syntax:

| Syntax                    | Example                                         |
| :------------------------ | :---------------------------------------------- |
| Literal object            | `{cherry: true, apple: true, mincemeat: false}` |
| Literal array             | `['cherries', 'flour', 'sugar']`                |
| Spread in literal array   | `['apples', 'flour', …]`                        |
| Calls                     | `bake(ingredients)`                             |
| New                       | `new Oven()`                                    |
| Property access           | `pie.slice`                                     |
| Array index               | `ingredients[0]`                                |
| Identity reference        | [`Component`](/api/core/Component)              |
| A template string         | `pie is ${multiplier} times better than cake`   |
| Literal string            | `'pi'`                                          |
| Literal number            | `3.14153265`                                    |
| Literal boolean           | `true`                                          |
| Literal null              | `null`                                          |
| Supported prefix operator | `!cake`                                         |
| Supported binary operator | `a+b`                                           |
| Conditional operator      | `a ? b : c`                                     |
| Parentheses               | `(a+b)`                                         |

If an expression uses unsupported syntax, the collector writes an error node to the `.metadata.json` file. The compiler later reports the error if it needs that piece of metadata to generate the application code.

**HELPFUL:** If you want `ngc` to report syntax errors immediately rather than produce a `.metadata.json` file with errors, set the `strictMetadataEmit` option in the TypeScript configuration file.

```
"angularCompilerOptions": {
  …
  "strictMetadataEmit" : true
}
```

Angular libraries have this option to ensure that all Angular `.metadata.json` files are clean and it is a best practice to do the same when building your own libraries.

### [Code folding](#code-folding)

The compiler can only resolve references to ***exported*** symbols. The collector, however, can evaluate an expression during collection and record the result in the `.metadata.json`, rather than the original expression. This allows you to make limited use of non-exported symbols within expressions.

For example, the collector can evaluate the expression `1 + 2 + 3 + 4` and replace it with the result, `10`. This process is called *folding*. An expression that can be reduced in this manner is *foldable*.

The collector can evaluate references to module-local `const` declarations and initialized `var` and `let` declarations, effectively removing them from the `.metadata.json` file.

Consider the following component definition:

```
const template = '<div>{{hero().name}}</div>';

@Component({
  selector: 'app-hero',
  template: template,
})
export class Hero {
  hero = input.required<Hero>();
}
```

The compiler could not refer to the `template` constant because it isn't exported. The collector, however, can fold the `template` constant into the metadata definition by in-lining its contents. The effect is the same as if you had written:

```
@Component({
  selector: 'app-hero',
  template: '<div>{{hero().name}}</div>',
})
export class Hero {
  hero = input.required<Hero>();
}
```

There is no longer a reference to `template` and, therefore, nothing to trouble the compiler when it later interprets the *collector's* output in `.metadata.json`.

You can take this example a step further by including the `template` constant in another expression:

```
const template = '<div>{{hero().name}}</div>';

@Component({
  selector: 'app-hero',
  template: template + '<div>{{hero().title}}</div>',
})
export class Hero {
  hero = input.required<Hero>();
}
```

The collector reduces this expression to its equivalent *folded* string:

```
'<div>{{hero().name}}</div><div>{{hero().title}}</div>';
```

#### [Foldable syntax](#foldable-syntax)

The following table describes which expressions the collector can and cannot fold:

| Syntax                           | Foldable                                 |
| :------------------------------- | :--------------------------------------- |
| Literal object                   | yes                                      |
| Literal array                    | yes                                      |
| Spread in literal array          | no                                       |
| Calls                            | no                                       |
| New                              | no                                       |
| Property access                  | yes, if target is foldable               |
| Array index                      | yes, if target and index are foldable    |
| Identity reference               | yes, if it is a reference to a local     |
| A template with no substitutions | yes                                      |
| A template with substitutions    | yes, if the substitutions are foldable   |
| Literal string                   | yes                                      |
| Literal number                   | yes                                      |
| Literal boolean                  | yes                                      |
| Literal null                     | yes                                      |
| Supported prefix operator        | yes, if operand is foldable              |
| Supported binary operator        | yes, if both left and right are foldable |
| Conditional operator             | yes, if condition is foldable            |
| Parentheses                      | yes, if the expression is foldable       |

If an expression is not foldable, the collector writes it to `.metadata.json` as an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for the compiler to resolve.

## [Phase 2: code generation](#phase-2-code-generation)

The collector makes no attempt to understand the metadata that it collects and outputs to `.metadata.json`. It represents the metadata as best it can and records errors when it detects a metadata syntax violation. It's the compiler's job to interpret the `.metadata.json` in the code generation phase.

The compiler understands all syntax forms that the collector supports, but it may reject *syntactically* correct metadata if the *semantics* violate compiler rules.

### [Public or protected symbols](#public-or-protected-symbols)

The compiler can only reference *exported symbols*.

* Decorated component class members must be public or protected. You cannot make an [`input()`](/api/core/input) property private.

* Data bound properties must also be public or protected

### [Supported classes and functions](#supported-classes-and-functions)

The collector can represent a function call or object creation with `new` as long as the syntax is valid. The compiler, however, can later refuse to generate a call to a *particular* function or creation of a *particular* object.

The compiler can only create instances of certain classes, supports only core decorators, and only supports calls to macros (functions or static methods) that return expressions.

| Compiler action      | Details                                                                                                                                 |
| :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| New instances        | The compiler only allows metadata that create instances of the class [`InjectionToken`](/api/core/InjectionToken) from `@angular/core`. |
| Supported decorators | The compiler only supports metadata for the [Angular decorators in the `@angular/core` module](/api?type=decorator).                    |

### [Functions and static method calls](#functions-and-static-method-calls)

The collector accepts any function or static method that contains a single `return` statement. The compiler, however, only supports macros in the form of functions or static methods that return an *expression*.

For example, consider the following function:

```
export function wrapInArray<T>(value: T): T[] {
  return [value];
}
```

You can call the `wrapInArray` in a metadata definition because it returns the value of an expression that conforms to the compiler's restrictive JavaScript subset.

You might use `wrapInArray()` like this:

```
@NgModule({
  declarations: wrapInArray(Typical),
})
export class TypicalModule {}
```

The compiler treats this usage as if you had written:

```
@NgModule({
  declarations: [Typical],
})
export class TypicalModule {}
```

The Angular [`RouterModule`](api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes. Review the [source code](https://github.com/angular/angular/blob/main/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code") for these methods to see how macros can simplify configuration of complex [NgModules](guide/ngmodules/overview).

## [Phase 3: Template type checking](#phase-3-template-type-checking)

One of the Angular compiler's most helpful features is the ability to type-check expressions within templates, and catch any errors before they cause crashes at runtime. In the template type-checking phase, the Angular template compiler uses the TypeScript compiler to validate the binding expressions in templates.

Strict checking in this phase is enabled by default and controlled by the compiler option `"strictTemplates"` in the `"angularCompilerOptions"` of the project's TypeScript configuration file (see [Angular Compiler Options](reference/configs/angular-compiler-options)).

Template validation produces error messages when a type error is detected in a template binding expression, similar to how type errors are reported by the TypeScript compiler against code in a `.ts` file.

For example, consider the following component:

```
@Component({
  selector: 'my-component',
  template: '{{person.addresss.street}}',
})
class MyComponent {
  person?: Person;
}
```

This produces the following error:

```
my.component.ts.MyComponent.html(1,1): : Property 'addresss' does not exist on type 'Person'. Did you mean 'address'?
```

The file name reported in the error message, `my.component.ts.MyComponent.html`, is a synthetic file generated by the template compiler that holds contents of the `MyComponent` class template. The compiler never writes this file to disk. The line and column numbers are relative to the template string in the [`@Component`](/api/core/Component) annotation of the class, `MyComponent` in this case. If a component uses `templateUrl` instead of `template`, the errors are reported in the HTML file referenced by the `templateUrl` instead of a synthetic file.

The error location is the beginning of the text node that contains the interpolation expression with the error. If the error is in an attribute binding such as `[value]="person.address.street"`, the error location is the location of the attribute that contains the error.

The validation uses the TypeScript type checker and the options supplied to the TypeScript compiler to control how detailed the type validation is. For example, if the `strictNullChecks` TypeScript option is enabled, the error

```
my.component.ts.MyComponent.html(1,1): : Object is possibly 'undefined'
```

is reported as well as the above error message.

### [Type narrowing](#type-narrowing)

The expression used in an [`ngIf`](/api/common/NgIf) directive is used to narrow type unions in the Angular template compiler, the same way the `if` expression does in TypeScript. For example, to avoid `Object is possibly 'undefined'` error in the template above, modify it to only emit the interpolation if the value of `person` is initialized as shown below:

```
@Component({
  selector: 'my-component',
  template: '<span *ngIf="person"> {{person.address.street}} </span>',
})
class MyComponent {
  person?: Person;
}
```

Using `*ngIf` allows the TypeScript compiler to infer that the `person` used in the binding expression will never be `undefined`.

For more information about input type narrowing, see [Improving template type checking for custom directives](/guide/directives/structural-directives#improving-template-type-checking-for-custom-directives).

### [Non-null type assertion operator](#non-null-type-assertion-operator)

Use the non-null type assertion operator to suppress the `Object is possibly 'undefined'` error when it is inconvenient to use `*ngIf` or when some constraint in the component ensures that the expression is always non-null when the binding expression is interpolated.

In the following example, the `person` and `address` properties are always set together, implying that `address` is always non-null if `person` is non-null. There is no convenient way to describe this constraint to TypeScript and the template compiler, but the error is suppressed in the example by using `address!.street`.

```
@Component({
  selector: 'my-component',
  template: '<span *ngIf="person"> {{person.name}} lives on {{address!.street}} </span>',
})
class MyComponent {
  person?: Person;
  address?: Address;

  setData(person: Person, address: Address) {
    this.person = person;
    this.address = address;
  }
}
```

The non-null assertion operator should be used sparingly as refactoring of the component might break this constraint.

In this example it is recommended to include the checking of `address` in the `*ngIf` as shown below:

```
@Component({
  selector: 'my-component',
  template: '<span *ngIf="person && address"> {{person.name}} lives on {{address.street}} </span>',
})
class MyComponent {
  person?: Person;
  address?: Address;

  setData(person: Person, address: Address) {
    this.person = person;
    this.address = address;
  }
}
```

----
url: https://angular.dev/cli/generate/environments
----

```
ng generate environments [options]
```

Generates and configures environment files for your project. Environment files allow you to define different settings and configurations for various environments, such as development, testing, and production. This schematic helps you create and manage these files, making it easier to customize your application's behavior for each environment.

## [Options](#options)

`project`

The name of the project where the environment files should be created or updated.

Value Type`string`

Jump to details

----
url: https://angular.dev/api/core/HostAttributeToken
----

```
    
      class HostAttributeToken {}
    
    
```

@paramattributeName`string`

@returns`HostAttributeToken`

@returns`string`

## [Usage Notes](#usage-notes)

### [Injecting an attribute that is known to exist](#injecting-an-attribute-that-is-known-to-exist)

```
@Directive()
class MyDir {
  attr: string = inject(new HostAttributeToken('some-attr'));
}
```

### [Optionally injecting an attribute](#optionally-injecting-an-attribute)

```
@Directive()
class MyDir {
  attr: string | null = inject(new HostAttributeToken('some-attr'), {optional: true});
}
```

Jump to details

----
url: https://angular.dev/api/common/DatePipeConfig
----

```
    
      interface DatePipeConfig {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/RouteConfigLoadEnd
----

## [API](/api/router/RouteConfigLoadEnd#api)

```
    
      class RouteConfigLoadEnd {}
    
    
```

@paramroute`Route`

@returns`RouteConfigLoadEnd`

Jump to details

----
url: https://angular.dev/api/router/withExperimentalAutoCleanupInjectors
----

Use [`withAutoCleanupInjectors`](/api/router/withAutoCleanupInjectors) instead.

## [API](#api)

```
function withExperimentalAutoCleanupInjectors(): AutoCleanupInjectorsFeature;
```

@deprecated

Use [`withAutoCleanupInjectors`](/api/router/withAutoCleanupInjectors) instead.

@returns`AutoCleanupInjectorsFeature`

Jump to details

----
url: https://angular.dev/cli/generate/module
----

```
ng generate module [name] [options]
```

```
ng generate m [name] [options]
```

Creates a new, generic NgModule definition in the given project.

## [Arguments](/cli/generate/module#arguments)

`name`

The name of the NgModule.

Value Type`string`

## [Options](/cli/generate/module#options)

`flat`

Create the new files at the top level of the current project root.

Value Type`boolean`Default`false`

`module`

Alias`m`

The declaring NgModule.

Value Type`string`

`project`

The name of the project.

Value Type`string`

`route`

The route path for a lazy-loaded module. When supplied, creates a component in the new module, and adds the route to that component in the `Routes` array declared in the module provided in the `--module` option.

Value Type`string`

`routing`

Create a routing module.

Value Type`boolean`Default`false`

`routing-scope`

The scope for the new routing module.

Value Type`string`Allowed Values`Child`, `Root`Default`Child`

`type-separator`

The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.module.ts`.

Value Type`string`Allowed Values`-`, `.`Default`-`

Jump to details

----
url: https://angular.dev/errors
----

## [Runtime errors](#runtime-errors)

| Code      | Name                                                                                 |
| :-------- | :----------------------------------------------------------------------------------- |
| `NG0100`  | [Expression Changed After Checked](errors/NG0100)                                    |
| `NG0200`  | [Circular Dependency in DI](errors/NG0200)                                           |
| `NG0201`  | [No Provider Found](errors/NG0201)                                                   |
| `NG0203`  | [`inject()` must be called from an injection context](errors/NG0203)                 |
| `NG0204`  | [Invalid Injection Token](errors/NG0204)                                             |
| `NG0205`  | [Injector has already been destroyed](errors/NG0205)                                 |
| `NG0207`  | [EnvironmentProviders in wrong context](errors/NG0207)                               |
| `NG0209`  | [Invalid multi provider](errors/NG0209)                                              |
| `NG0300`  | [Selector Collision](errors/NG0300)                                                  |
| `NG0301`  | [Export Not Found](errors/NG0301)                                                    |
| `NG0302`  | [Pipe Not Found](errors/NG0302)                                                      |
| `NG0318`  | [Invalid style property binding value](errors/NG0318)                                |
| `NG0401`  | [Missing platform](errors/NG0401)                                                    |
| `NG0403`  | [Bootstrapped NgModule doesn't specify which component to initialize](errors/NG0403) |
| `NG0500`  | [Hydration Node Mismatch](errors/NG0500)                                             |
| `NG0501`  | [Hydration Missing Siblings](errors/NG0501)                                          |
| `NG0502`  | [Hydration Missing Node](errors/NG0502)                                              |
| `NG0503`  | [Hydration Unsupported Projection of DOM Nodes](errors/NG0503)                       |
| `NG0504`  | [Skip hydration flag is applied to an invalid node](errors/NG0504)                   |
| `NG0505`  | [No hydration info in server response](errors/NG0505)                                |
| `NG0506`  | [NgZone remains unstable](errors/NG0506)                                             |
| `NG0507`  | [HTML content was altered after SSR](errors/NG0507)                                  |
| `NG0600`  | [Signal write in a disallowed context](errors/NG0600)                                |
| `NG0602`  | [Disallowed function call inside reactive context](errors/NG0602)                    |
| `NG0750`  | [@defer dependencies failed to load](errors/NG0750)                                  |
| `NG0751`  | [@defer behavior when HMR is enabled](errors/NG0751)                                 |
| `NG0904`  | [Unsafe value used in a resource URL context](errors/NG0904)                         |
| `NG0910`  | [Unsafe bindings on an iframe element](errors/NG0910)                                |
| `NG0912`  | [Component ID generation collision](errors/NG0912)                                   |
| `NG0913`  | [Runtime Performance Warnings](errors/NG0913)                                        |
| `NG0919`  | [Circular Dependency Detected](errors/NG0919)                                        |
| `NG0950`  | [Required input is accessed before a value is set](errors/NG0950)                    |
| `NG0951`  | [Child query result is required but no value is available](errors/NG0951)            |
| `NG0955`  | [Track expression resulted in duplicated keys for a given collection](errors/NG0955) |
| `NG0956`  | [Tracking expression caused re-creation of the DOM structure](errors/NG0956)         |
| `NG0991`  | [Resource completed before producing a value](errors/NG0991)                         |
| `NG01002` | [Missing Control Value](errors/NG01002)                                              |
| `NG01101` | [Wrong Async Validator Return Type](errors/NG01101)                                  |
| `NG01203` | [Missing value accessor](errors/NG01203)                                             |
| `NG01902` | [Orphan field in signal forms](errors/NG01902)                                       |
| `NG02200` | [Missing Iterable Differ](errors/NG02200)                                            |
| `NG02800` | [JSONP support in HttpClient configuration](errors/NG02800)                          |
| `NG02802` | [Headers not transferred by HttpTransferCache](errors/NG02802)                       |
| `NG02825` | [Fetch response body exceeds the configured limit](errors/NG02825)                   |
| `NG05000` | [Hydration with unsupported Zone.js instance.](errors/NG05000)                       |
| `NG05104` | [Root element was not found.](errors/NG05104)                                        |
| `NG05106` | [Insertion reference node not found](errors/NG05106)                                 |
| `NG05201` | [Unsafe resource URL passed to DomSanitizer](errors/NG05201)                         |
| `NG05703` | [Suspicious URL origin change during SSR](errors/NG05703)                            |

## [Compiler errors](#compiler-errors)

| Code     | Name                                                        |
| :------- | :---------------------------------------------------------- |
| `NG1001` | [Argument Not Literal](errors/NG1001)                       |
| `NG2003` | [Missing Token](errors/NG2003)                              |
| `NG2009` | [Invalid Shadow DOM selector](errors/NG2009)                |
| `NG3003` | [Import Cycle Detected](errors/NG3003)                      |
| `NG6100` | [NgModule.id Set to module.id anti-pattern](errors/NG6100)  |
| `NG8001` | [Invalid Element](errors/NG8001)                            |
| `NG8002` | [Invalid Attribute](errors/NG8002)                          |
| `NG8003` | [Missing Reference Target](errors/NG8003)                   |
| `NG8011` | [Control Flow Preventing Content Projection](errors/NG8011) |
| `NG8023` | [Multiple Components Match Same Element](errors/NG8023)     |
| `NG8024` | [Conflicting Host Directive Binding](errors/NG8024)         |

----
url: https://angular.dev/guide/legacy-animations/reusable-animations
----

This topic provides examples of how to create reusable animations.

## [Create reusable animations](#create-reusable-animations)

To create a reusable animation, use the [`animation()`](api/animations/animation) function to define an animation in a separate `.ts` file and declare this animation definition as a `const` export variable. You can then import and reuse this animation in your application components using the [`useAnimation()`](api/animations/useAnimation) function.

### animations.ts

```
import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';

export const transitionAnimation = animation([
  style({
    height: '{{ height }}',
    opacity: '{{ opacity }}',
    backgroundColor: '{{ backgroundColor }}',
  }),
  animate('{{ time }}'),
]);
```

In the preceding code snippet, `transitionAnimation` is made reusable by declaring it as an export variable.

**HELPFUL:** The `height`, `opacity`, `backgroundColor`, and `time` inputs are replaced during runtime.

You can also export a part of an animation. For example, the following snippet exports the animation `trigger`.

### animations.ts

```
import {animation, style, animate, trigger, transition, useAnimation} from '@angular/animations';
export const triggerAnimation = trigger('openClose', [
  transition('open => closed', [
    useAnimation(transitionAnimation, {
      params: {
        height: 0,
        opacity: 1,
        backgroundColor: 'red',
        time: '1s',
      },
    }),
  ]),
]);
```

From this point, you can import reusable animation variables into your component class. For example, the following code snippet imports the `transitionAnimation` variable and uses it via the [`useAnimation()`](/api/animations/useAnimation) function.

### open-close.ts

```
import {Component, input} from '@angular/core';
import {transition, trigger, useAnimation, AnimationEvent} from '@angular/animations';
import {transitionAnimation} from './animations';

@Component({
  selector: 'app-open-close-reusable',
  animations: [
    trigger('openClose', [
      transition('open => closed', [
        useAnimation(transitionAnimation, {
          params: {
            height: 0,
            opacity: 1,
            backgroundColor: 'red',
            time: '1s',
          },
        }),
      ]),
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
```

## [More on Angular animations](#more-on-angular-animations)

You might also be interested in the following:

----
url: https://angular.dev/api/core/WebMcpToolExecute
----

```
    
      type WebMcpToolExecute<InputSchema extends JsonSchemaForInference> = (  args: InferArgsFromInputSchema<InputSchema>,  client: Client,) => unknown
    
    
```

Jump to details

----
url: https://angular.dev/api/service-worker/UnrecoverableStateEvent
----

```
    
      interface UnrecoverableStateEvent {}
    
    
```

## [Description](#description)

An event emitted when the version of the app used by the service worker to serve this client is in a broken state that cannot be recovered from and a full page reload is required.

For example, the service worker may not be able to retrieve a required resource, neither from the cache nor from the server. This could happen if a new version is deployed to the server and the service worker cache has been partially cleaned by the browser, removing some files of a previous app version but not all.

Jump to details

----
url: https://angular.dev/guide/i18n/deploy
----

If `myapp` is the directory that contains the distributable files of your project, you typically make different versions available for different locales in locale directories. For example, your French version is located in the `myapp/fr` directory and the Spanish version is located in the `myapp/es` directory.

The HTML `base` tag with the `href` attribute specifies the base URI, or URL, for relative links. If you set the `"localize"` option in [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file to `true` or to an array of locale IDs, the CLI adjusts the base `href` for each version of the application. To adjust the base `href` for each version of the application, the CLI adds the locale to the configured `"subPath"`. Specify the `"subPath"` for each locale in your [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file. The following example displays `"subPath"` set to an empty string.

### angular.json

```
"projects": {
    "angular.io-example": {
      // ...
      "i18n": {
        "sourceLocale": "en-US",
        "locales": {
          "fr": {
            "translation": "src/locale/messages.fr.xlf",
            "subPath": ""
          }
        }
      },
      "architect": {
        // ...
      }
    }
  }
  // ...
}
```

## [Configure a server](/guide/i18n/deploy#configure-a-server)

Typical deployment of multiple languages serve each language from a different subdirectory. Users are redirected to the preferred language defined in the browser using the `Accept-Language` HTTP header. If the user has not defined a preferred language, or if the preferred language is not available, then the server falls back to the default language. To change the language, change your current location to another subdirectory. The change of subdirectory often occurs using a menu implemented in the application.

For more information on how to deploy apps to a remote server, see [Deployment](tools/cli/deployment "Deployment | Angular").

**IMPORTANT:** If you are using [Server rendering](guide/ssr) with `outputMode` set to `server`, Angular automatically handles redirection dynamically based on the `Accept-Language` HTTP header. This simplifies deployment by eliminating the need for manual server or configuration adjustments.

### [Nginx example](/guide/i18n/deploy#nginx-example)

The following example displays an Nginx configuration.

```
http {
    # Browser preferred language detection (does NOT require
    # AcceptLanguageModule)
    map $http_accept_language $accept_language {
        ~*^de de;
        ~*^fr fr;
        ~*^en "";
    }
    # ...
}

server {
    listen 80;
    server_name localhost;
    root /www/data;

    # Fallback to default language if no preference defined by browser
    if ($accept_language ~ "^$") {
        set $accept_language "fr";
    }

    # Redirect "/" to Angular application in the preferred language of the browser
    rewrite ^/$ /$accept_language permanent;

    # Everything under the Angular application is always redirected to Angular in the
    # correct language
    location ~ ^/(fr|de|$) {
        if ($accept_language = "") {
            try_files $uri /index.html?$args;
        }
        try_files $uri /$1/index.html?$args;
    }
    # ...
}
```

### [Apache example](/guide/i18n/deploy#apache-example)

The following example displays an Apache configuration.

```
<VirtualHost *:80>
    ServerName localhost
    DocumentRoot /www/data

    <Directory "/www/data">
        # Enable rewrite engine for URL manipulation
        RewriteEngine on
        RewriteBase /

        # Serve 'index.html' for root-level access
        RewriteRule ^../index\.html$ - [L]

        # If the requested file or directory does not exist, redirect to 'index.html'
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule (..) $1/index.html [L]

        # Language-based redirection based on HTTP Accept-Language header

        # Redirect German users to the '/de/' directory
        RewriteCond %{HTTP:Accept-Language} ^de [NC]
        RewriteRule ^$ /de/ [R]

        # Redirect English-speaking users to the '/en/' directory
        RewriteCond %{HTTP:Accept-Language} ^en [NC]
        RewriteRule ^$ /en/ [R]

        # Redirect users with unsupported languages (not 'en' or 'de') to the '/fr/' directory
        RewriteCond %{HTTP:Accept-Language} !^en [NC]
        RewriteCond %{HTTP:Accept-Language} !^de [NC]
        RewriteRule ^$ /fr/ [R]
    </Directory>
</VirtualHost>
```

----
url: https://angular.dev/tools/language-service
----

The Angular Language Service provides code editors with a way to get completions, errors, hints, and navigation inside Angular templates. It works with external templates in separate HTML files, and also with in-line templates.

## [Configuring compiler options for the Angular Language Service](#configuring-compiler-options-for-the-angular-language-service)

To enable the latest Language Service features, set the `strictTemplates` option in `tsconfig.json` by setting `strictTemplates` to `true`, as shown in the following example:

```
"angularCompilerOptions": {
  "strictTemplates": true
}
```

For more information, see the [Angular compiler options](reference/configs/angular-compiler-options) guide.

## [Features](#features)

Your editor autodetects that you are opening an Angular file. It then uses the Angular Language Service to read your `tsconfig.json` file, find all the templates you have in your application, and then provide language services for any templates that you open.

Language services include:

* Completions lists
* AOT Diagnostic messages
* Quick info
* Go to definition

### [Autocompletion](#autocompletion)

Autocompletion can speed up your development time by providing you with contextual possibilities and hints as you type. This example shows autocomplete in an interpolation. As you type it out, you can press tab to complete.

There are also completions within elements. Any elements you have as a component selector will show up in the completion list.

### [Error checking](#error-checking)

The Angular Language Service can forewarn you of mistakes in your code. In this example, Angular doesn't know what `orders` is or where it comes from.

### [Quick info and navigation](#quick-info-and-navigation)

The quick-info feature lets you hover to see where components, directives, and modules come from. You can then click "Go to definition" or press F12 to go directly to the definition.

## [Angular Language Service in your editor](#angular-language-service-in-your-editor)

Angular Language Service is currently available as an extension for [Visual Studio Code](https://code.visualstudio.com), [WebStorm](https://www.jetbrains.com/webstorm), [Sublime Text](https://www.sublimetext.com), [Zed](https://zed.dev), [Neovim](https://neovim.io), and [Eclipse IDE](https://www.eclipse.org/eclipseide).

**NOTE:** The Angular team maintains and distributes the Visual Studio Code extension, which is supported in Visual Studio Code and VS Code-based editors such as Cursor and Antigravity. Integrations for other editors are maintained by their respective editor vendors or by the community. Report issues specific to those integrations to their maintainers.

### [Visual Studio Code](#visual-studio-code)

In [Visual Studio Code](https://code.visualstudio.com), install the extension from the [Extensions: Marketplace](https://marketplace.visualstudio.com/items?itemName=Angular.ng-template). Open the marketplace from the editor using the Extensions icon on the left menu pane, or use VS Quick Open (⌘+P on Mac, CTRL+P on Windows) and type "? ext". In the marketplace, search for Angular Language Service extension, and click the **Install** button.

The Visual Studio Code integration with the Angular language service is maintained and distributed by the Angular team.

### [Visual Studio](#visual-studio)

In [Visual Studio](https://visualstudio.microsoft.com), install the extension from the [Extensions: Marketplace](https://marketplace.visualstudio.com/items?itemName=TypeScriptTeam.AngularLanguageService). Open the marketplace from the editor selecting Extensions on the top menu pane, and then selecting Manage Extensions. In the marketplace, search for Angular Language Service extension, and click the **Install** button.

The Visual Studio integration with the Angular language service is maintained and distributed by Microsoft with help from the Angular team. Check out the project [here](https://github.com/microsoft/vs-ng-language-service).

### [WebStorm](#webstorm)

In [WebStorm](https://www.jetbrains.com/webstorm), enable the plugin [Angular and AngularJS](https://plugins.jetbrains.com/plugin/6971-angular-and-angularjs).

Since WebStorm 2019.1, the `@angular/language-service` is not required anymore and should be removed from your `package.json`.

### [Sublime Text](#sublime-text)

In [Sublime Text](https://www.sublimetext.com), the Language Service supports only in-line templates when installed as a plug-in. You need a custom Sublime plug-in (or modifications to the current plug-in) for completions in HTML files.

To use the Language Service for in-line templates, you must first add an extension to allow TypeScript, then install the Angular Language Service plug-in. Starting with TypeScript 2.3, TypeScript has a plug-in model that the language service can use.

1. Install the latest version of TypeScript in a local `node_modules` directory:

   ```
   npm install --save-dev typescript
   ```

2. Install the Angular Language Service package in the same location:

   ```
   npm install --save-dev @angular/language-service
   ```

3. Once the package is installed, add the following to the `"compilerOptions"` section of your project's `tsconfig.json`.

   ### tsconfig.json

   ```
   "plugins": [
     {"name": "@angular/language-service"}
   ]
   ```

4. In your editor's user preferences (`Cmd+,` or `Ctrl+,`), add the following:

   ### Sublime Text user preferences

   ```
   "typescript-tsdk": "<path to your folder>/node_modules/typescript/lib"
   ```

This lets the Angular Language Service provide diagnostics and completions in `.ts` files.

### [Eclipse IDE](#eclipse-ide)

Either directly install the "Eclipse IDE for Web and JavaScript developers" package which comes with the Angular Language Server included, or from other Eclipse IDE packages, use Help > Eclipse Marketplace to find and install [Eclipse Wild Web Developer](https://marketplace.eclipse.org/content/wild-web-developer-html-css-javascript-typescript-nodejs-angular-json-yaml-kubernetes-xml).

### [Neovim](#neovim)

#### [Conquer of Completion with Node.js](#conquer-of-completion-with-nodejs)

The Angular Language Service uses the tsserver, which doesn't follow the LSP specifications exactly. Therefore if you are using neovim or vim with JavaScript or TypeScript or Angular you may find that [Conquer of Completion](https://github.com/neoclide/coc.nvim) (COC) has the fullest implementation of the Angular Language Service and the tsserver. This is because COC ports the VSCode implementation of the tsserver which accommodates the tsserver's implementation.

1. [Setup coc.nvim](https://github.com/neoclide/coc.nvim)

2. Configure the Angular Language Service

   Once installed run the `CocConfig` vim command line command to open the config file `coc-settings.json` and add the angular property.

   Make sure to substitute the correct paths to your global `node_modules` such that they go to directories which contain `tsserver` and the `ngserver` respectively.

   ### CocConfig example file coc-settings.json

   ```
   {
     "languageserver": {
       "angular": {
         "command": "ngserver",
         "args": [
           "--stdio",
           "--tsProbeLocations",
           "/usr/local/lib/node_modules/typescript/lib/CHANGE/THIS/TO/YOUR/GLOBAL/NODE_MODULES",
           "--ngProbeLocations",
           "/usr/local/lib/node_modules/@angular/language-server/bin/CHANGE/THIS/TO/YOUR/GLOBAL/NODE_MODULES"
         ],
         "filetypes": ["ts", "typescript", "html"],
         "trace.server.verbosity": "verbose"
       }
     }
   }
   ```

**HELPFUL:** `/usr/local/lib/node_modules/typescript/lib` and `/usr/local/lib/node_modules/@angular/language-server/bin` above should point to the location of your global node modules, which may be different.

#### [Built In Neovim LSP](#built-in-neovim-lsp)

Angular Language Service can be used with Neovim by using the [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) plugin.

1. [Install nvim-lspconfig](https://github.com/neovim/nvim-lspconfig?tab=readme-ov-file#install)

2. [Configure angularls for nvim-lspconfig](https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#angularls)

### [Zed](#zed)

In [Zed](https://zed.dev), install the community-maintained extension from [Extensions: Marketplace](https://zed.dev/extensions/angular).

## [How the Language Service works](#how-the-language-service-works)

When you use an editor with a language service, the editor starts a separate language-service process and communicates with it through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call), using the [Language Server Protocol](https://microsoft.github.io/language-server-protocol). When you type into the editor, the editor sends information to the language-service process to track the state of your project.

When you trigger a completion list within a template, the editor first parses the template into an HTML [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree). The Angular compiler interprets that tree to determine the context: which module the template is part of, the current scope, the component selector, and where your cursor is in the template AST. It can then determine the symbols that could potentially be at that position.

It's a little more involved if you are in an interpolation. If you have an interpolation of `{{data.---}}` inside a `div` and need the completion list after `data.---`, the compiler can't use the HTML AST to find the answer. The HTML AST can only tell the compiler that there is some text with the characters "`{{data.---}}`". That's when the template parser produces an expression AST, which resides within the template AST. The Angular Language Services then looks at `data.---` within its context, asks the TypeScript Language Service what the members of `data` are, and returns the list of possibilities.

## [More information](#more-information)

* For more in-depth information on the implementation, see the [Angular Language Service source](https://github.com/angular/angular/blob/main/packages/language-service/src)
* For more on the design considerations and intentions, see [design documentation here](https://github.com/angular/vscode-ng-language-service/wiki/Design)

----
url: https://angular.dev/api/core/Type
----

```
    
      const Type: FunctionConstructor;
    
    
```

## [Description](#description)

Represents a type that a Component or other object is instances of.

An example of a [`Type`](/api/core/Type) is `MyCustomComponent` class, which in JavaScript is represented by the `MyCustomComponent` constructor function.

Jump to details

----
url: https://angular.dev/errors/NG6100
----

Using `module.id` as an NgModule `id` is a common anti-pattern and is likely not serving a useful purpose in your code.

NgModules can be declared with an `id`:

```
@NgModule({
  id: 'my_module',
})
export class MyModule {}
```

Declaring an `id` makes the NgModule available for lookup via the [`getNgModuleById()`](/api/core/getNgModuleById) operation. This functionality is rarely used, mainly in very specific bundling scenarios when lazily loading NgModules without obtaining direct references to them. In most Angular code, ES dynamic `import()` (`import('./path/to/module')`) should be used instead, as this provides a direct reference to the NgModule being loaded without the need for a global registration side effect.

If you are not using [`getNgModuleById`](/api/core/getNgModuleById), you do not need to provide `id`s for your NgModules. Providing one has a significant drawback: it makes the NgModule non-tree-shakable, which can have an impact on your bundle size.

In particular, the pattern of specifying `id: module.id` results from a misunderstanding of [`@NgModule.id`](/api/core/NgModule#id). In earlier versions of Angular, it was sometimes necessary to include the property `moduleId: module.id` in [`@Component`](/api/core/Component) metadata.

## [Debugging the error](#debugging-the-error)

You can remove the `id: module.id` declaration from your NgModules. The compiler ignores this declaration and issues this warning instead.

----
url: https://angular.dev/guide/testing/using-component-harnesses
----

### [CDK Installation](#cdk-installation)

The [Component Dev Kit (CDK)](https://material.angular.dev/cdk/categories) is a set of behavior primitives for building components. To use the component harnesses, first install `@angular/cdk` from npm. You can do this from your terminal using the Angular CLI:

```
ng add @angular/cdk
```

## [Test harness environments and loaders](#test-harness-environments-and-loaders)

You can use component test harnesses in different test environments. Angular CDK supports two built-in environments:

* Unit tests with Angular's [`TestBed`](/api/core/testing/TestBed)
* End-to-end tests with [WebDriver](https://developer.mozilla.org/en-US/docs/Web/WebDriver)

Each environment provides a **harness loader**. The loader creates the harness instances you use throughout your tests. See below for more specific guidance on supported testing environments.

Additional testing environments require custom bindings. See the [adding harness support for additional testing environments guide](guide/testing/component-harnesses-testing-environments) for more information.

### [Using the loader from `TestbedHarnessEnvironment` for unit tests](#using-the-loader-from-testbedharnessenvironment-for-unit-tests)

For unit tests you can create a harness loader from [TestbedHarnessEnvironment](/api/cdk/testing/testbed/TestbedHarnessEnvironment). This environment uses a [component fixture](api/core/testing/ComponentFixture) created by Angular's [`TestBed`](/api/core/testing/TestBed).

To create a harness loader rooted at the fixture's root element, use the `loader()` method:

```
const fixture = TestBed.createComponent(MyComponent);

// Create a harness loader from the fixture
const loader = TestbedHarnessEnvironment.loader(fixture);
...

// Use the loader to get harness instances
const myComponentHarness = await loader.getHarness(MyComponent);
```

To create a harness loader for harnesses for elements that fall outside the fixture, use the `documentRootLoader()` method. For example, code that displays a floating element or pop-up often attaches DOM elements directly to the document body, such as the `Overlay` service in Angular CDK.

You can also create a harness loader directly with `harnessForFixture()` for a harness at that fixture's root element directly.

### [Using the loader from `SeleniumWebDriverHarnessEnvironment` for end-to-end tests](#using-the-loader-from-seleniumwebdriverharnessenvironment-for-end-to-end-tests)

For WebDriver-based end-to-end tests you can create a harness loader with [`SeleniumWebDriverHarnessEnvironment`](/api/cdk/testing/selenium-webdriver/SeleniumWebDriverHarnessEnvironment).

Use the `loader()` method to get the harness loader instance for the current HTML document, rooted at the document's root element. This environment uses a WebDriver client.

```
let wd: webdriver.WebDriver = getMyWebDriverClient();
const loader = SeleniumWebDriverHarnessEnvironment.loader(wd);
...
const myComponentHarness = await loader.getHarness(MyComponent);
```

## [Using a harness loader](#using-a-harness-loader)

Harness loader instances correspond to a specific DOM element and are used to create component harness instances for elements under that specific element.

To get [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for the first instance of the element, use the `getHarness()` method. To get all [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances, use the `getAllHarnesses()` method.

```
// Get harness for first instance of the element
const myComponentHarness = await loader.getHarness(MyComponent);

// Get harnesses for all instances of the element
const myComponentHarnesses = await loader.getAllHarnesses(MyComponent);
```

In addition to `getHarness` and `getAllHarnesses`, [`HarnessLoader`](/api/cdk/testing/HarnessLoader) has several other useful methods for querying for harnesses:

* `getHarnessAtIndex(...)`: Gets the harness for a component that matches the given criteria at a specific index.
* `countHarnesses(...)`: Counts the number of component instances that match the given criteria.
* `hasHarness(...)`: Checks if at least one component instance matches the given criteria.

As an example, consider a reusable dialog-button component that opens a dialog on click. It contains the following components, each with a corresponding harness:

* `MyDialogButton` (composes the `MyButton` and `MyDialog` with a convenient API)
* `MyButton` (a standard button component)
* `MyDialog` (a dialog appended to `document.body` by `MyDialogButton` upon click)

The following test loads harnesses for each of these components:

```
let fixture: ComponentFixture<MyDialogButton>;
let loader: HarnessLoader;
let rootLoader: HarnessLoader;

beforeEach(() => {
  fixture = TestBed.createComponent(MyDialogButton);
  loader = TestbedHarnessEnvironment.loader(fixture);
  rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
});

it('loads harnesses', async () => {
  // Load a harness for the bootstrapped component with `harnessForFixture`
  dialogButtonHarness = await TestbedHarnessEnvironment.harnessForFixture(
    fixture,
    MyDialogButtonHarness,
  );

  // The button element is inside the fixture's root element, so we use `loader`.
  const buttonHarness = await loader.getHarness(MyButtonHarness);

  // Click the button to open the dialog
  await buttonHarness.click();

  // The dialog is appended to `document.body`, outside of the fixture's root element,
  // so we use `rootLoader` in this case.
  const dialogHarness = await rootLoader.getHarness(MyDialogHarness);

  // ... make some assertions
});
```

### [Harness behavior in different environments](#harness-behavior-in-different-environments)

Harnesses may not behave exactly the same in all environments. Some differences are unavoidable between the real user interaction versus the simulated events generated in unit tests. Angular CDK makes a best effort to normalize the behavior to the extent possible.

### [Interacting with child elements](#interacting-with-child-elements)

To interact with elements below the root element of this harness loader, use the [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance of a child element. For the first instance of the child element, use the `getChildLoader()` method. For all instances of the child element, use the `getAllChildLoaders()` method.

```
const myComponentHarness = await loader.getHarness(MyComponent);

// Get loader for first instance of child element with '.child' selector
const childLoader = await myComponentHarness.getLoader('.child');

// Get loaders for all instances of child elements with '.child' selector
const allChildLoaders = await myComponentHarness.getAllChildLoaders('.child');
```

### [Filtering harnesses](#filtering-harnesses)

When a page contains multiple instances of a particular component, you may want to filter based on some property of the component to get a particular component instance. You can use a **harness predicate**, a class used to associate a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) class with predicates functions that can be used to filter component instances, to do so.

When you ask a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) for a harness, you're actually providing a HarnessQuery. A query can be one of two things:

* A harness constructor. This just gets that harness
* A [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate), which gets harnesses that are filtered based on one or more conditions

[`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) does support some base filters (selector, ancestor) that work on anything that extends [`ComponentHarness`](/api/cdk/testing/ComponentHarness).

```
// Example of loading a MyButtonComponentHarness with a harness predicate
const disabledButtonPredicate = new HarnessPredicate(MyButtonComponentHarness, {
  selector: '[disabled]',
});
const disabledButton = await loader.getHarness(disabledButtonPredicate);
```

However it's common for harnesses to implement a static `with()` method that accepts component-specific filtering options and returns a [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate).

```
// Example of loading a MyButtonComponentHarness with a specific selector
const button = await loader.getHarness(MyButtonComponentHarness.with({selector: 'btn'}));
```

For more details refer to the specific harness documentation since additional filtering options are specific to each harness implementation.

## [Using test harness APIs](#using-test-harness-apis)

While every harness defines an API specific to its corresponding component, they all share a common base class, [ComponentHarness](/api/cdk/testing/ComponentHarness). This base class defines a static property, `hostSelector`, that matches the harness class to instances of the component in the DOM.

Beyond that, the API of any given harness is specific to its corresponding component; refer to the component's documentation to learn how to use a specific harness.

As an example, the following is a test for a component that uses the [Angular Material slider component harness](https://material.angular.dev/components/slider/api#MatSliderHarness):

```
it('should get value of slider thumb', async () => {
  const slider = await loader.getHarness(MatSliderHarness);
  const thumb = await slider.getEndThumb();
  expect(await thumb.getValue()).toBe(50);
});
```

## [Interop with Angular change detection](#interop-with-angular-change-detection)

By default, test harnesses runs Angular's [change detection](/best-practices/runtime-performance) before reading the state of a DOM element and after interacting with a DOM element.

There may be times that you need finer-grained control over change detection in your tests. such as checking the state of a component while an async operation is pending. In these cases use the [`manualChangeDetection`](/api/cdk/testing/manualChangeDetection) function to disable automatic handling of change detection for a block of code.

```
it('checks state while async action is in progress', async () => {
  const buttonHarness = loader.getHarness(MyButtonHarness);
  await manualChangeDetection(async () => {
    await buttonHarness.click();
    fixture.detectChanges();
    // Check expectations while async click operation is in progress.
    expect(isProgressSpinnerVisible()).toBe(true);
    await fixture.whenStable();
    // Check expectations after async click operation complete.
    expect(isProgressSpinnerVisible()).toBe(false);
  });
});
```

Almost all harness methods are asynchronous and return a `Promise` to support the following:

* Support for unit tests
* Support for end-to-end tests
* Insulate tests against changes in asynchronous behavior

The Angular team recommends using [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) to improve the test readability. Calling `await` blocks the execution of your test until the associated `Promise` resolves.

Occasionally, you may want to perform multiple actions simultaneously and wait until they're all done rather than performing each action sequentially. For example, read multiple properties of a single component. In these situations use the [`parallel`](/api/cdk/testing/parallel) function to parallelize the operations. The parallel function works similarly to `Promise.all`, while also optimizing change detection checks.

```
it('reads properties in parallel', async () => {
  const checkboxHarness = loader.getHarness(MyCheckboxHarness);
  // Read the checked and intermediate properties simultaneously.
  const [checked, indeterminate] = await parallel(() => [
    checkboxHarness.isChecked(),
    checkboxHarness.isIndeterminate(),
  ]);
  expect(checked).toBe(false);
  expect(indeterminate).toBe(true);
});
```

----
url: https://angular.dev/style-guide
----

## [Introduction](/style-guide#introduction)

This guide covers a range of style conventions for Angular application code. These recommendations are not required for Angular to work, but instead establish a set of coding practices that promote consistency across the Angular ecosystem. A consistent set of practices makes it easier to share code and move between projects.

This guide does *not* cover TypeScript or general coding practices unrelated to Angular. For TypeScript, check out [Google's TypeScript style guide](https://google.github.io/styleguide/tsguide.html).

### [When in doubt, prefer consistency](/style-guide#when-in-doubt-prefer-consistency)

Whenever you encounter a situation in which these rules contradict the style of a particular file, prioritize maintaining consistency within a file. Mixing different style conventions in a single file creates more confusion than diverging from the recommendations in this guide.

## [Naming](/style-guide#naming)

### [Separate words in file names with hyphens](/style-guide#separate-words-in-file-names-with-hyphens)

Separate words within a file name with hyphens (`-`). For example, a component named `UserProfile` has a file name `user-profile.ts`.

### [Use the same name for a file's tests with `.spec` at the end](/style-guide#use-the-same-name-for-a-files-tests-with-spec-at-the-end)

For unit tests, end file names with `.spec.ts`. For example, the unit test file for the `UserProfile` component has the file name `user-profile.spec.ts`.

### [Match file names to the TypeScript identifier within](/style-guide#match-file-names-to-the-typescript-identifier-within)

File names should generally describe the contents of the code in the file. When the file contains a TypeScript class, the file name should reflect that class name. For example, a file containing a component named `UserProfile` has the name `user-profile.ts`.

If the file contains more than one primary namable identifier, choose a name that describes the common theme to the code within. If the code in a file does not fit within a common theme or feature area, consider breaking the code up into different files. Avoid overly generic file names like `helpers.ts`, `utils.ts`, or `common.ts`.

### [Use the same file name for a component's TypeScript, template, and styles](/style-guide#use-the-same-file-name-for-a-components-typescript-template-and-styles)

Components typically consist of one TypeScript file, one template file, and one style file. These files should share the same name with different file extensions. For example, a `UserProfile` component can have the files `user-profile.ts`, `user-profile.html`, and `user-profile.css`.

If a component has more than one style file, append the name with additional words that describe the styles specific to that file. For example, `UserProfile` might have style files `user-profile-settings.css` and `user-profile-subscription.css`.

## [Project structure](/style-guide#project-structure)

### [All the application's code goes in a directory named `src`](/style-guide#all-the-applications-code-goes-in-a-directory-named-src)

All of your Angular UI code (TypeScript, HTML, and styles) should live inside a directory named `src`. Code that's not related to UI, such as configuration files or scripts, should live outside the `src` directory.

This keeps the root application directory consistent between different Angular projects and creates a clear separation between UI code and other code in your project.

### [Bootstrap your application in a file named `main.ts` directly inside `src`](/style-guide#bootstrap-your-application-in-a-file-named-maints-directly-inside-src)

The code to start up, or **bootstrap**, an Angular application should always live in a file named `main.ts`. This represents the primary entry point to the application.

### [Group closely related files together in the same directory](/style-guide#group-closely-related-files-together-in-the-same-directory)

Angular components consist of a TypeScript file and, optionally, a template and one or more style files. You should group these together in the same directory.

Unit tests should live in the same directory as the code-under-test. Avoid collecting unrelated tests into a single `tests` directory.

### [Organize your project by feature areas](/style-guide#organize-your-project-by-feature-areas)

Organize your project into subdirectories based on the features of your application or common themes to the code in those directories. For example, the project structure for a movie theater site, MovieReel, might look like this:

```
src/
├─ movie-reel/
│ ├─ show-times/
│ │ ├─ film-calendar/
│ │ ├─ film-details/
│ ├─ reserve-tickets/
│ │ ├─ payment-info/
│ │ ├─ purchase-confirmation/
```

Avoid creating subdirectories based on the type of code that lives in those directories. For example, avoid creating directories like `components`, `directives`, and `services`.

Avoid putting so many files into one directory that it becomes hard to read or navigate. As the number of files in a directory grows, consider splitting further into additional sub-directories.

### [One concept per file](/style-guide#one-concept-per-file)

Prefer focusing source files on a single *concept*. For Angular classes specifically, this usually means one component, directive, or service per file. However, it's okay if a file contains more than one component or directive if your classes are relatively small and they tie together as part of a single concept.

When in doubt, go with the approach that leads to smaller files.

## [Dependency injection](/style-guide#dependency-injection)

### [Prefer the `inject` function over constructor parameter injection](/style-guide#prefer-the-inject-function-over-constructor-parameter-injection)

Prefer using the [`inject`](/api/core/inject) function over injecting constructor parameters. The [`inject`](/api/core/inject) function works the same way as constructor parameter injection, but offers several style advantages:

* [`inject`](/api/core/inject) is generally more readable, especially when a class injects many dependencies.
* It's more syntactically straightforward to add comments to injected dependencies
* [`inject`](/api/core/inject) offers better type inference.
* When targeting ES2022+ with [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields), you can avoid separating field declaration and initialization when fields read on injected dependencies.

[You can refactor existing code to `inject` with an automatic tool](reference/migrations/inject-function).

## [Components and directives](/style-guide#components-and-directives)

### [Choosing component selectors](/style-guide#choosing-component-selectors)

See the [Components guide for details on choosing component selectors](guide/components/selectors#choosing-a-selector).

### [Naming component and directive members](/style-guide#naming-component-and-directive-members)

See the Components guide for details on [naming input properties](guide/components/inputs#choosing-input-names) and [naming output properties](guide/components/outputs#choosing-event-names).

### [Choosing directive selectors](/style-guide#choosing-directive-selectors)

Directives should use the same [application-specific prefix](guide/components/selectors#selector-prefixes) as your components.

When using an attribute selector for a directive, use a camelCase attribute name. For example, if your application is named "MovieReel" and you build a directive that adds a tooltip to an element, you might use the selector `[mrTooltip]`.

### [Group Angular-specific properties before methods](/style-guide#group-angular-specific-properties-before-methods)

Components and directives should group Angular-specific properties together, typically near the top of the class declaration. This includes injected dependencies, inputs, outputs, and queries. Define these and other properties before the class's methods.

This practice makes it easier to find the class's template APIs and dependencies.

### [Keep components and directives focused on presentation](/style-guide#keep-components-and-directives-focused-on-presentation)

Code inside your components and directives should generally relate to the UI shown on the page. For code that makes sense on its own, decoupled from the UI, prefer refactoring to other files. For example, you can factor form validation rules or data transformations into separate functions or classes.

### [Avoid overly complex logic in templates](/style-guide#avoid-overly-complex-logic-in-templates)

Angular templates are designed to accommodate [JavaScript-like expressions](guide/templates/expression-syntax). You should take advantage of these expressions to capture relatively straightforward logic directly in template expressions.

When the code in a template gets too complex, though, refactor logic into the TypeScript code (typically with a [computed](guide/signals#computed-signals)).

There's no one hard-and-fast rule that determines what constitutes "complex". Use your best judgement.

### [Use `protected` on class members that are only used by a component's template](/style-guide#use-protected-on-class-members-that-are-only-used-by-a-components-template)

A component class's public members intrinsically define a public API that's accessible via dependency injection and [queries](guide/components/queries). Prefer `protected` access for any members that are meant to be read from the component's template.

```
@Component({
  ...,
  template: `<p>{{ fullName() }}</p>`,
})
export class UserProfile {
  firstName = input();
  lastName = input();

// `fullName` is not part of the component's public API, but is used in the template.
  protected fullName = computed(() => `${this.firstName()} ${this.lastName()}`);
}
```

### [Use `readonly` for properties that shouldn't change](/style-guide#use-readonly-for-properties-that-shouldnt-change)

Mark component and directive properties initialized by Angular as `readonly`. This includes properties initialized by `input`, [`model`](/api/core/model), [`output`](/api/core/output), and queries. The readonly access modifier ensures that the value set by Angular is not overwritten.

```
@Component(/* ... */)
export class UserProfile {
  readonly userId = input();
  readonly userSaved = output();
  readonly userName = model();
}
```

For components and directives that use the decorator-based [`@Input`](/api/core/Input), [`@Output`](/api/core/Output), and query APIs, this advice applies to output properties and queries, but not input properties.

```
@Component(/* ... */)
export class UserProfile {
  @Output() readonly userSaved = new EventEmitter<void>();
  @ViewChildren(PaymentMethod) readonly paymentMethods?: QueryList<PaymentMethod>;
}
```

### [Prefer `class` and `style` over `ngClass` and `ngStyle`](/style-guide#prefer-class-and-style-over-ngclass-and-ngstyle)

Prefer `class` and `style` bindings over using the [`NgClass`](/api/common/NgClass) and [`NgStyle`](/api/common/NgStyle) directives.

Prefer

```
<div [class.admin]="isAdmin" [class.dense]="density === 'high'">
  <div [style.color]="textColor" [style.background-color]="backgroundColor">
    <!-- OR -->
    <div [class]="{admin: isAdmin, dense: density === 'high'}">
      <div [style]="{'color': textColor, 'background-color': backgroundColor}"></div>
    </div>
  </div>
</div>
```

Avoid

```
<div [ngClass]="{admin: isAdmin, dense: density === 'high'}">
  <div [ngStyle]="{'color': textColor, 'background-color': backgroundColor}"></div>
</div>
```

Both `class` and `style` bindings use a more straightforward syntax that aligns closely with standard HTML attributes. This makes your templates easier to read and understand, especially for developers familiar with basic HTML.

Additionally, the [`NgClass`](/api/common/NgClass) and [`NgStyle`](/api/common/NgStyle) directives incur an additional performance cost compared to the built-in `class` and `style` binding syntax.

For more details, refer to the [bindings guide](/guide/templates/binding#css-class-and-style-property-bindings)

### [Name event handlers for what they *do*, not for the triggering event](/style-guide#name-event-handlers-for-what-they-do-not-for-the-triggering-event)

Prefer naming event handlers for the action they perform rather than for the triggering event:

Prefer

```
<button (click)="saveUserData()">Save</button>
```

Avoid

```
<button (click)="handleClick()">Save</button>
```

Using meaningful names like this makes it easier to tell what an event does from reading the template.

For keyboard events, you can use Angular's key event modifiers with specific handler names:

```
<textarea (keydown.control.enter)="commitNotes()" (keydown.control.space)="showSuggestions()">
```

Sometimes, event handling logic is especially long or complex, making it impractical to declare a single well-named handler. In these cases, it's fine to fall back to a name like 'handleKeydown' and then delegate to more specific behaviors based on the event details:

```
@Component(/* ... */)
class RichText {
  handleKeydown(event: KeyboardEvent) {
    if (event.ctrlKey) {
      if (event.key === 'B') {
        this.activateBold();
      } else if (event.key === 'I') {
        this.activateItalic();
      }
      // ...
    }
  }
}
```

### [Keep lifecycle methods simple](/style-guide#keep-lifecycle-methods-simple)

Avoid putting long or complex logic inside lifecycle hooks like `ngOnInit`. Instead, prefer creating well-named methods to contain that logic and then *call those methods* in your lifecycle hooks. Lifecycle hook names describe *when* they run, meaning that the code inside doesn't have a meaningful name that describes what the code inside is doing.

Prefer

```
ngOnInit() {
  this.startLogging();
  this.runBackgroundTask();
}
```

Avoid

```
ngOnInit() {
  this.logger.setMode('info');
  this.logger.monitorErrors();
  // ...and all the rest of the code that would be unrolled from these methods.
}
```

### [Use lifecycle hook interfaces](/style-guide#use-lifecycle-hook-interfaces)

Angular provides a TypeScript interface for each lifecycle method. When adding a lifecycle hook to your class, import and `implement` these interfaces to ensure that the methods are named correctly.

```
import {Component, OnInit} from '@angular/core';

@Component(/* ... */)
export class UserProfile implements OnInit {
  // The `OnInit` interface ensures this method is named correctly.
  ngOnInit() {
    /* ... */
  }
}
```

----
url: https://angular.dev/api/animations/AnimationQueryMetadata
----

```
    
      interface AnimationQueryMetadata extends AnimationMetadata {}
    
    
```

The CSS selector for this query.

One or more animation step objects.

A query options object.

Jump to details

----
url: https://angular.dev/api/platform-browser/EventManagerPlugin
----

```
    
      abstract class EventManagerPlugin {}
    
    
```

@param\_doc`any`

@returns`EventManagerPlugin`

Should return `true` for every event name that should be supported by this plugin

@parameventName`string`

@returns`boolean`

Implement the behaviour for the supported events

@paramelement`HTMLElement`

@parameventName`string`

@paramhandler`Function`

@paramoptions`ListenerOptions | undefined`

@returns`Function`

## [Description](#description)

The plugin definition for the [`EventManager`](/api/platform-browser/EventManager) class

It can be used as a base class to create custom manager plugins, i.e. you can create your own class that extends the [`EventManagerPlugin`](/api/platform-browser/EventManagerPlugin) one.

Jump to details

----
url: https://angular.dev/errors/NG0500
----

This error means that during the hydration process, Angular expected a DOM structure as rendered and annotated during server-side rendering. However, on the client, the DOM tree was different than the server rendered DOM tree.

This error typically happens due to direct DOM manipulation using native browser APIs that alter the DOM structure outside of what Angular produced. It will also occur if you use `innerHTML` or `outerHTML` to set HTML content, which similarly alters the DOM structure outside of what Angular produced. You can resolve this by refactoring the component to use native Angular APIs instead of native APIs. If that's not possible, you can add the `ngSkipHydration` attribute to your component's host node, which will disable hydration for the component and its children. `ngSkipHydration` should only be used as a last resort and should be considered a bug that needs to be fixed.

More information about hydration can be found in [this guide](guide/hydration).

The following example will trigger the error.

```
@Component({
  selector: 'app-example',
  template: '<div><span>world</span></div>',
})
export class Example {
  hostElement = inject(ElementRef).nativeElement;

  ngOnInit() {
    // Create a new <p> element with the `Hello` text inside
    const newNode = document.createElement('p');
    newNode.innerHTML = 'Hello';

    // Insert the <p> before the first element. Since Angular has no information
    // about the <p> element, it will be looking for the <div> element at the first
    // element position instead. As a result, a hydration mismatch error would be
    // thrown. Instead, update component's template to create the <p> element.
    this.hostElement.insertBefore(newNode, this.hostElement.firstChild);
  }
}
```

## [Debugging the error](#debugging-the-error)

The error message in the developer console should contain information on a specific part of the application's DOM structure that is causing the problem. Review that part of the application for hydration-related errors, such as direct DOM manipulation using native APIs.

Check that your template has valid HTML structure. See more information in the [hydration guide](guide/hydration#valid-html-structure).

You can add the `ngSkipHydration` attribute to your component host node as a possible workaround.

If your application works in development environment, but you see hydration errors in production builds, make sure that the generated HTML that is delivered to a browser contains comment nodes produced by Angular during the rendering on the server. Those comment nodes are used by Angular runtime as anchors for view containers (with and without hydration) and hydration process expects them to be present at their original locations. If you have a custom logic to remove comment nodes from the HTML generated by the server-side rendering or you've configured your CDN to remove them before serving the content - disable the comment nodes removal and check if hydration errors are gone.

----
url: https://angular.dev/api/core/rxjs-interop/rxResource
----

```
function rxResource<T, R>(
  opts: RxResourceOptions<T, R> & { defaultValue: NoInfer<T> },
): ResourceRef<T>;
function rxResource<T, R>(
  opts: RxResourceOptions<T, R>,
): ResourceRef<T | undefined>;
```

Like `resource` but uses an RxJS based `loader` which maps the request to an `Observable` of the resource's value.

@paramopts`RxResourceOptions<T, R> & { defaultValue: NoInfer<T>; }`

@returns`ResourceRef<T>`

Like `resource` but uses an RxJS based `loader` which maps the request to an `Observable` of the resource's value.

@paramopts`RxResourceOptions<T, R>`

@returns`ResourceRef<T | undefined>`

Jump to details

----
url: https://angular.dev/api/common/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ValueProvider
----

```
    
      interface ValueProvider extends ValueSansProvider {}
    
    
```

An injection token. Typically an instance of [`Type`](/api/core/Type) or [`InjectionToken`](/api/core/InjectionToken), but can be `any`.

When true, injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

The value to inject.

## [Usage Notes](#usage-notes)

### [Example](#example)

```
const injector = Injector.create({providers: [{provide: String, useValue: 'Hello'}]});

        expect(injector.get(String)).toEqual('Hello');
```

### [Multi-value example](#multi-value-example)

```
const locale = new InjectionToken<string[]>('locale');
        const injector = Injector.create({
          providers: [
            {provide: locale, multi: true, useValue: 'en'},
            {provide: locale, multi: true, useValue: 'sk'},
          ],
        });

        const locales: string[] = injector.get(locale);
        expect(locales).toEqual(['en', 'sk']);
```

Jump to details

----
url: https://angular.dev/api/core/untracked
----

## [API](/api/core/untracked#api)

```
function untracked<T>(nonReactiveReadsFn: () => T): T;
```

@paramnonReactiveReadsFn`() => T`

@returns`T`

Jump to details

----
url: https://angular.dev/guide/forms/signals/schemas
----

Signal Forms uses a two-layer architecture to separate *how your form is structured* from *how it behaves at runtime*.

When you pass a schema function to [`form()`](/api/forms/signals/form), that function *runs once* during form creation. Its job is to set up the form's logic tree by declaring which fields have validation, which fields are disabled, and which fields depend on other fields. This is the **structural layer** of your form.

Inside a schema function, you call rule functions such as [`disabled()`](/api/forms/signals/disabled) and [`validate()`](/api/forms/signals/validate). These rule functions accept reactive logic that recomputes whenever the signals they reference change. Conditional rules like [`disabled()`](/api/forms/signals/disabled) and `required()` accept optional configuration, including a `when` function that activates the rule. Together, these form the **behavioral layer** of your form during runtime.

```
contactForm = form(this.contactModel, (schemaPath) => {
  // Schema function: runs ONCE during form creation
  required(schemaPath.name);
  disabled(schemaPath.couponCode, {when: ({valueOf}) => valueOf(schemaPath.total) < 50});
  //  ^^^ Reactive logic: recomputes when total changes
});
```

This distinction is important when you compose schemas because functions like [`apply()`](/api/forms/signals/apply), [`applyWhen()`](/api/forms/signals/applyWhen), and [`schema()`](/api/forms/signals/schema) all operate at the structural layer. Schemas control *which* rules exist and *whether* they're active, while rule functions define *what* those rules evaluate.

## [Create reusable schemas with `schema()`](#create-reusable-schemas-with-schema)

When multiple forms share the same rules for a common data shape, you can use the [`schema()`](/api/forms/signals/schema) function to extract those rules into a reusable schema.

```
import {schema, required, minLength} from '@angular/forms/signals';

const nameSchema = schema<{first: string; last: string}>((name) => {
  required(name.first);
  required(name.last);
  minLength(name.first, 2);
  minLength(name.last, 2);
});
```

The [`schema()`](/api/forms/signals/schema) function wraps a function and converts it into a reusable `Schema<T>` object. Like any schema function, it *runs once* per form, but the object itself can be shared across as many forms as you need.

**TIP:** If rules only appear in one place, an inline schema function works just as well. Use [`schema()`](/api/forms/signals/schema) when you want to reuse the same schema across multiple forms or apply the same schema to multiple paths. Reusable [`Schema`](/api/forms/signals/Schema) objects are cached per form compilation.

### [Using the schema with `apply()`](#using-the-schema-with-apply)

You can apply a reusable schema to a specific path in a form by using the [`apply()`](/api/forms/signals/apply) function. When you call [`apply()`](/api/forms/signals/apply), the schema receives a scoped path that only sees the fields within that sub-path:

```
import {apply} from '@angular/forms/signals';

profileForm = form(this.profileModel, (schemaPath) => {
  apply(schemaPath.name, nameSchema);
});

registrationForm = form(this.registrationModel, (schemaPath) => {
  apply(schemaPath.name, nameSchema);
});
```

## [Conditional schemas with `applyWhen()`](#conditional-schemas-with-applywhen)

**NOTE:** The [Adding form logic guide](guide/forms/signals/form-logic) introduced [`applyWhen()`](/api/forms/signals/applyWhen) for conditional rules with inline logic. This section covers how to compose [`applyWhen()`](/api/forms/signals/applyWhen) with reusable schemas.

Some rules should only apply under certain conditions. For example, a zip code field might require validation only when the selected country is the United States.

The [`applyWhen()`](/api/forms/signals/applyWhen) function applies a schema conditionally based on reactive state. It accepts three arguments:

1. A path to apply the schema to
2. A reactive logic function that returns `true` when the schema should be active
3. A schema or schema function containing the conditional rules

```
import {form, applyWhen, required, pattern} from '@angular/forms/signals';

addressForm = form(this.addressModel, (schemaPath) => {
  applyWhen(
    schemaPath,
    ({valueOf}) => valueOf(schemaPath.country) === 'US',
    (schemaPath) => {
      required(schemaPath.zipCode);
      pattern(schemaPath.zipCode, /^\d{5}(-\d{4})?$/);
    },
  );
});
```

The logic function receives a [`FieldContext`](/api/forms/signals/FieldContext), which provides access to `value`, `valueOf`, `stateOf`, and other reactive helpers. Because it's reactive, the condition is re-evaluated whenever the signals it reads change. When the condition becomes `false`, the rules inside the schema deactivate. When it becomes `true` again, they reactivate.

The schema itself is still structural — the schema function runs once during form creation. The condition controls whether those rules are *active*, not whether they *exist*.

Inside the conditional schema, use the scoped path parameter passed to that schema function. Paths from an outer schema are not valid inside a nested schema.

### [Combining `applyWhen()` with reusable schemas](#combining-applywhen-with-reusable-schemas)

Since [`applyWhen()`](/api/forms/signals/applyWhen) accepts a [`Schema`](/api/forms/signals/Schema) object, you can pair it with [`schema()`](/api/forms/signals/schema) to conditionally apply reusable schemas:

```
const usZipCodeSchema = schema<{zipCode: string}>((address) => {
  required(address.zipCode);
  pattern(address.zipCode, /^\d{5}(-\d{4})?$/);
});

const caPostalCodeSchema = schema<{postalCode: string}>((address) => {
  required(address.postalCode);
  pattern(address.postalCode, /^[A-Z]\d[A-Z] \d[A-Z]\d$/);
});

shippingForm = form(this.shippingModel, (schemaPath) => {
  applyWhen(
    schemaPath.address,
    ({valueOf}) => valueOf(schemaPath.country) === 'US',
    usZipCodeSchema,
  );
  applyWhen(
    schemaPath.address,
    ({valueOf}) => valueOf(schemaPath.country) === 'CA',
    caPostalCodeSchema,
  );
});
```

**NOTE:** The logic function accesses `valueOf(schemaPath.country)` even though the path argument is `schemaPath.address`. This is because the `valueOf` helper can access any field in the form, not just fields within the scoped path.

This pattern keeps validation logic modular — each country's address rules live in their own schema, and the form selects which one to activate based on the user's choice.

## [Type-narrowing with `applyWhenValue()`](#type-narrowing-with-applywhenvalue)

The [`applyWhenValue()`](/api/forms/signals/applyWhenValue) function simplifies conditions that only need to check the field's value. Instead of receiving a [`FieldContext`](/api/forms/signals/FieldContext), the condition function receives the field's raw value directly.

### applyWhen — logic function receives FieldContext

```
applyWhen(schemaPath.payment, ({value}) => value().type === 'credit-card', creditCardSchema);
```

### applyWhenValue — condition receives the value directly

```
applyWhenValue(schemaPath.payment, (payment) => payment.type === 'credit-card', creditCardSchema);
```

The main advantage of [`applyWhenValue()`](/api/forms/signals/applyWhenValue) is TypeScript type guard support. When the condition function is a type guard, the schema's type parameter narrows to the guarded type. This is especially useful for discriminated unions, where each variant has different fields that need different rules.

```
import {form, applyWhenValue, required} from '@angular/forms/signals';

interface CreditCard {
  type: 'credit-card';
  cardNumber: string;
  expiry: string;
  cvv: string;
}

interface BankTransfer {
  type: 'bank-transfer';
  accountNumber: string;
  routingNumber: string;
}

type PaymentMethod = CreditCard | BankTransfer;

function isCreditCard(value: PaymentMethod): value is CreditCard {
  return value.type === 'credit-card';
}

function isBankTransfer(value: PaymentMethod): value is BankTransfer {
  return value.type === 'bank-transfer';
}

paymentForm = form(this.paymentModel, (schemaPath) => {
  applyWhenValue(schemaPath, isCreditCard, (payment) => {
    // TypeScript knows payment is scoped to CreditCard
    required(payment.cardNumber);
    required(payment.expiry);
    required(payment.cvv);
  });

  applyWhenValue(schemaPath, isBankTransfer, (payment) => {
    // TypeScript knows payment is scoped to BankTransfer
    required(payment.accountNumber);
    required(payment.routingNumber);
  });
});
```

Without the type guard, TypeScript would not know which fields are available inside each schema function. The type narrowing ensures that accessing `payment.cardNumber` is type-safe in the credit card branch and `payment.accountNumber` is type-safe in the bank transfer branch.

## [Array items with `applyEach()`](#array-items-with-applyeach)

When a form contains an array of objects, you often need the same rules applied to every item. The [`applyEach()`](/api/forms/signals/applyEach) function applies a schema to each item in an array field, regardless of how many items exist.

```
import {form, applyEach, required, min} from '@angular/forms/signals';

type LineItem = {name: string; quantity: number};

orderForm = form(this.orderModel, (schemaPath) => {
  required(schemaPath.title);

  applyEach(schemaPath.items, (item) => {
    required(item.name);
    min(item.quantity, 1);
  });
});
```

The schema function passed to [`applyEach()`](/api/forms/signals/applyEach) receives a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) scoped to a single array item. Rules declared inside apply to every item in the array, including items added after form creation.

### [Combining `applyEach()` with reusable schemas](#combining-applyeach-with-reusable-schemas)

Since [`applyEach()`](/api/forms/signals/applyEach) accepts a [`Schema`](/api/forms/signals/Schema) object, you can extract item-level rules into a reusable schema and share them across forms:

```
const lineItemSchema = schema<LineItem>((item) => {
  required(item.name);
  min(item.quantity, 1);
});

orderForm = form(this.orderModel, (schemaPath) => {
  required(schemaPath.title);
  applyEach(schemaPath.items, lineItemSchema);
});

invoiceForm = form(this.invoiceModel, (schemaPath) => {
  required(schemaPath.invoiceNumber);
  applyEach(schemaPath.lineItems, lineItemSchema);
});
```

**TIP:** For more on validating array items, including custom error messages per field, see the [Validation guide](guide/forms/signals/validation).

## [Next steps](#next-steps)

To learn more about Signal Forms, check out these related guides:

* [Adding form logic](guide/forms/signals/form-logic) - Learn how to add conditional logic, dynamic behavior, and metadata to your forms
* [Validation](guide/forms/signals/validation) - Learn about validation rules and error handling
* [Async operations](guide/forms/signals/async-operations) - Learn how to handle form submission and async validation

----
url: https://angular.dev/api/router/withInMemoryScrolling
----

```
function withInMemoryScrolling(
  options?: InMemoryScrollingOptions,
): InMemoryScrollingFeature;
```

@paramoptions`InMemoryScrollingOptions`

Set of configuration parameters to customize scrolling behavior, see [`InMemoryScrollingOptions`](/api/router/InMemoryScrollingOptions) for additional information.

@returns`InMemoryScrollingFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can enable scrolling feature:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withInMemoryScrolling())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/errors/NG01203
----

For all custom form controls, you must register a value accessor.

Here's an example of how to provide one:

```
providers: [
  {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => MyInputField),
    multi: true,
  },
];
```

## [Debugging the error](#debugging-the-error)

As described above, your control was expected to have a value accessor, but was missing one. However, there are many different reasons this can happen in practice. Here's a listing of some known problems leading to this error.

1. If you **defined** a custom form control, did you remember to provide a value accessor?
2. Did you put [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) on an element with no value, or an **invalid element** (e.g. `<div [(ngModel)]="foo">`)?
3. Are you using a custom form control declared inside an [`NgModule`](/api/core/NgModule)? if so, make sure you are **importing** the [`NgModule`](/api/core/NgModule).
4. Are you using [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) with a third-party custom form control? Check whether that control provides a value accessor. If not, use **[`ngDefaultControl`](/api/forms/DefaultValueAccessor)** on the control's element.
5. Are you **testing** a custom form control? Be sure to configure your testbed to know about the control. You can do so with `Testbed.configureTestingModule`.
6. Are you using **Nx and Module Federation** with webpack? Your `webpack.config.js` may require [extra configuration](https://github.com/angular/angular/issues/43821#issuecomment-1054845431) to ensure the forms package is shared.

----
url: https://angular.dev/cli/generate/interface
----

```
ng generate interface [name] [type] [options]
```

```
ng generate i [name] [type] [options]
```

Creates a new interface in your project. Interfaces define the structure of objects in TypeScript, ensuring type safety and code clarity. This schematic generates a new interface with the specified name and type.

## [Arguments](#arguments)

`name`

The name for the new interface. This will be used to create the interface file (e.g., `my-interface.interface.ts`).

Value Type`string`

`type`

Adds a custom type to the filename, allowing you to create more descriptive interface names. For example, if you set the type to `data`, the filename will be `my-interface.data.ts`.

Value Type`string`

## [Options](#options)

`prefix`

A prefix to be added to the interface name. This is typically not used for interfaces, as they don't have selectors like components or directives.

Value Type`string`

`project`

The name of the project where the interface should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

Jump to details

----
url: https://angular.dev/tutorials/signal-forms/4-display-errors
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/3-add-validation)[](/tutorials/signal-forms/5-add-submission)

[](/tutorials/signal-forms/3-add-validation)[](/tutorials/signal-forms/5-add-submission)

Now that you're able to validate the form, it's important to show validation errors to users.

In this activity, you'll learn how to:

* Access field state with validation signals
* Use `@if` to conditionally display errors
* Loop through errors with `@for`
* Show errors only after user interaction

Let's display validation feedback!

***

1. ### [Add error display for email field](#add-error-display-for-email-field)

   Below the email input, add conditional error display. This will only show errors when the field is both invalid and touched:

   ```
   <label>
     Email
     <input type="email" [formField]="loginForm.email" />
   </label>
   @if (loginForm.email().invalid() && loginForm.email().touched()) {
     <div class="error">
       @for (error of loginForm.email().errors(); track error.kind) {
         <span>{{ error.message }}</span>
       }
     </div>
   }
   ```

   The `loginForm.email()` call accesses the field's state signal. The `invalid()` method returns `true` when validation fails, `touched()` returns `true` after the user has interacted with the field, and `errors()` provides an array of validation errors with their custom messages.

2. ### [Add error display for password field](#add-error-display-for-password-field)

   Below the password input, add the same pattern for password errors:

   ```
   <label>
     Password
     <input type="password" [formField]="loginForm.password" />
   </label>
   @if (loginForm.password().invalid() && loginForm.password().touched()) {
     <div class="error">
       @for (error of loginForm.password().errors(); track error.kind) {
         <span>{{ error.message }}</span>
       }
     </div>
   }
   ```

Excellent! You've added error display to your form. The errors appear only after users interact with a field, providing helpful feedback without being intrusive.

Next, you'll learn [how to handle form submission](/tutorials/signal-forms/5-add-submission)!

----
url: https://angular.dev/api/animations/AnimationFactory
----

```
    
      abstract class AnimationFactory {}
    
    
```

Creates an [`AnimationPlayer`](/api/animations/AnimationPlayer) instance for the reusable animation defined by the `AnimationBuilder.build()` method that created this factory and attaches the new player a DOM element.

@paramelement`any`

The DOM element to which to attach the player.

@paramoptions`AnimationOptions | undefined`

A set of options that can include a time delay and additional developer-defined parameters.

@returns`AnimationPlayer`

Jump to details

----
url: https://angular.dev/api/core/ANIMATION_MODULE_TYPE
----

## [API](/api/core/ANIMATION_MODULE_TYPE#api)

```
    
      const ANIMATION_MODULE_TYPE: InjectionToken<"NoopAnimations" | "BrowserAnimations">;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/SchemaPath
----

## [API](/api/forms/signals/SchemaPath#api)

```
    
      type SchemaPath<TValue, TSupportsRules extends SchemaPathRules = SchemaPathRules.Supported, TPathKind extends PathKind = PathKind.Root> = {  [ɵɵTYPE]: {    value: () => TValue;    supportsRules: TSupportsRules;    pathKind: TPathKind;  };}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ContentChild
----

```
@ContentChild(
  selector: ProviderToken<unknown> | Function | string,
  opts?: { descendants?: boolean; read?: any; static?: boolean },
);
```

## [Description](#description)

Property decorator that configures a content query.

Use to get the first element or the directive matching the selector from the content DOM. If the content DOM changes, and a new child matches the selector, the property will be updated.

Does not retrieve elements or directives that are in other components' templates, since a component's template is always a black box to its ancestors.

**Metadata Properties**:

* **selector** - The directive type or the name used for querying.
* **descendants** - If `true` (default) include all descendants of the element. If `false` then only query direct children of the element.
* **read** - Used to read a different token from the queried element.
* **static** - True to resolve query results before change detection runs, false to resolve after change detection. Defaults to false.

The following selectors are supported.

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* A template reference variable as a string (e.g. query `<my-component #cmp></my-component>` with `@ContentChild('cmp')`)
* Any provider defined in the child component tree of the current component (e.g. `@ContentChild(SomeService) someService: SomeService`)
* Any provider defined through a string token (e.g. `@ContentChild('someToken') someTokenVal: any`)
* A [`TemplateRef`](/api/core/TemplateRef) (e.g. query `<ng-template></ng-template>` with `@ContentChild(TemplateRef) template;`)

The following values are supported by `read`:

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* Any provider defined on the injector of the component that is matched by the `selector` of this query
* Any provider defined through a string token (e.g. `{provide: 'token', useValue: 'val'}`)
* [`TemplateRef`](/api/core/TemplateRef), [`ElementRef`](/api/core/ElementRef), [`ViewContainerRef`](/api/core/ViewContainerRef), and [`Injector`](/api/core/Injector)

Difference between dynamic and static queries:

| Queries                                                                                        | Details                                              |
| :--------------------------------------------------------------------------------------------- | :--------------------------------------------------- |
| Dynamic queries (`static: false`)                                                              | The query resolves before the `ngAfterContentInit()` |
| callback is called. The result will be updated for changes to your view, such as changes to    |                                                      |
| `ngIf` and `ngFor` blocks.                                                                     |                                                      |
| the view has been created, but before change detection runs (before the `ngOnInit()` callback  |                                                      |
| is called). The result, though, will never be updated to reflect changes to your view, such as |                                                      |
| changes to `ngIf` and `ngFor` blocks.                                                          |                                                      |

## [Usage Notes](#usage-notes)

```
import {AfterContentInit, ContentChild, Directive} from '@angular/core';

@Directive({selector: 'child-directive'})
class ChildDirective {}

@Directive({selector: 'someDir'})
class SomeDir implements AfterContentInit {
  @ContentChild(ChildDirective) contentChild!: ChildDirective;

  ngAfterContentInit() {
    // contentChild is set
  }
}
```

### [Example](#example)

```
import {Component, contentChild, Directive, input, signal} from '@angular/core';

@Directive({
  selector: 'pane',
})
export class Pane {
  id = input.required<string>();
}

@Component({
  selector: 'tab',
  template: ` <div>pane: {{ pane()?.id() }}</div> `,
})
export class Tab {
  pane = contentChild(Pane);
}

@Component({
  selector: 'example-app',
  imports: [Tab, Pane],
  template: `
    <tab>
      @if (shouldShow()) {
        <pane id="1" />
      } @else {
        <pane id="2" />
      }
    </tab>

    <button (click)="toggle()">Toggle</button>
  `,
})
export class ContentChildComp {
  shouldShow = signal(true);

  toggle() {
    this.shouldShow.update((shouldShow) => !shouldShow);
  }
}
```

Jump to details

----
url: https://angular.dev/api/forms/AbstractFormDirective
----

```
    
      abstract class AbstractFormDirective extends ControlContainer implements Form ,OnChanges ,OnDestroy {}
    
    
```

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@paramcallSetDisabledState`SetDisabledStateOption | undefined`

@returns`AbstractFormDirective`

Reports whether the form submission has been triggered.

Tracks the list of added [`FormControlName`](/api/forms/FormControlName) instances

Tracks the form bound to this directive.

Emits an event when the form submission has been triggered.

@paramchanges`{ [propName: string]: SimpleChange<any>; }`

@returns`void`

@returns`void`

@paramchanges`{ [propName: string]: SimpleChange<any>; }`

@returns`void`

@returns`void`

Returns this directive's instance.

Returns the Form bound to this directive.

Returns an array representing the path to this group. Because this directive always lives at the top level of a form, it always an empty array.

Method that sets up the control directive in this group, re-calculates its value and validity, and adds the instance to the internal list of directives.

@paramdir`FormControlName`

The [`FormControlName`](/api/forms/FormControlName) directive instance.

@returns`FormControl<any>`

Retrieves the [`FormControl`](/api/forms/FormControl) instance from the provided [`FormControlName`](/api/forms/FormControlName) directive

@paramdir`FormControlName`

The [`FormControlName`](/api/forms/FormControlName) directive instance.

@returns`FormControl<any>`

Removes the [`FormControlName`](/api/forms/FormControlName) instance from the internal list of directives

@paramdir`FormControlName`

The [`FormControlName`](/api/forms/FormControlName) directive instance.

@returns`void`

Adds a new [`FormGroupName`](/api/forms/FormGroupName) directive instance to the form.

@paramdir`FormGroupName`

The [`FormGroupName`](/api/forms/FormGroupName) directive instance.

@returns`void`

Performs the necessary cleanup when a [`FormGroupName`](/api/forms/FormGroupName) directive instance is removed from the view.

@paramdir`FormGroupName`

The [`FormGroupName`](/api/forms/FormGroupName) directive instance.

@returns`void`

Retrieves the [`FormGroup`](/api/forms/FormGroup) for a provided [`FormGroupName`](/api/forms/FormGroupName) directive instance

@paramdir`FormGroupName`

The [`FormGroupName`](/api/forms/FormGroupName) directive instance.

@returns`FormGroup<any>`

Retrieves the [`FormArray`](/api/forms/FormArray) for a provided [`FormArrayName`](/api/forms/FormArrayName) directive instance.

@paramdir`FormArrayName`

The [`FormArrayName`](/api/forms/FormArrayName) directive instance.

@returns`FormArray<any>`

Performs the necessary setup when a [`FormArrayName`](/api/forms/FormArrayName) directive instance is added to the view.

@paramdir`FormArrayName`

The [`FormArrayName`](/api/forms/FormArrayName) directive instance.

@returns`void`

Performs the necessary cleanup when a [`FormArrayName`](/api/forms/FormArrayName) directive instance is removed from the view.

@paramdir`FormArrayName`

The [`FormArrayName`](/api/forms/FormArrayName) directive instance.

@returns`void`

Sets the new value for the provided [`FormControlName`](/api/forms/FormControlName) directive.

@paramdir`FormControlName`

The [`FormControlName`](/api/forms/FormControlName) directive instance.

@paramvalue`any`

The new value for the directive's control.

@returns`void`

Method called when the "reset" event is triggered on the form.

@returns`void`

Resets the form to an initial value and resets its submitted status.

@paramvalue`any`

The new value for the form.

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; }`

@returns`void`

Method called with the "submit" event is triggered on the form. Triggers the `ngSubmit` emitter to emit the "submit" event as its payload.

@param$event`Event`

The "submit" event object

@returns`boolean`

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

Jump to details

----
url: https://angular.dev/api/router/NavigationErrorHandlerFeature
----

```
    
      type NavigationErrorHandlerFeature = RouterFeature<RouterFeatureKind.NavigationErrorHandlerFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/ssr/AngularAppEngine
----

```
    
      class AngularAppEngine {}
    
    
```

Creates a new instance of the Angular server application engine.

@paramoptions`AngularAppEngineOptions | undefined`

Options for the Angular server application engine.

@returns`AngularAppEngine`

Handles an incoming HTTP request by serving prerendered content, performing server-side rendering, or delivering a static file for client-side rendered routes based on the [`RenderMode`](/api/ssr/RenderMode) setting.

@paramrequest`Request`

* The HTTP request to handle.

Resolution: Authorize your hostname by configuring `allowedHosts` in `angular.json` in: `projects.[project-name].architect.build.options.security.allowedHosts`. Alternatively, you pass it directly through the configuration options of [`AngularAppEngine`](/api/ssr/AngularAppEngine).

For more information see: <https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf>

## [Usage Notes](#usage-notes)

This class should be instantiated once and used as a singleton across the server-side application to ensure consistent handling of rendering requests and resource management.

Jump to details

----
url: https://angular.dev/essentials/signal-forms
----

# Forms with signals

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/signal-forms.md "Edit this page")

Signal Forms manage form state using Angular signals to provide automatic synchronization between your data model and the UI with Angular Signals.

This guide walks you through the core concepts to create forms with Signal Forms. Here's how it works:

## [Creating your first form](#creating-your-first-form)

### [1. Create a form model with `signal()`](#1-create-a-form-model-with-signal)

Every form starts by creating a signal that holds your form's data model:

```
interface LoginData {
  email: string;
  password: string;
}

const loginModel = signal<LoginData>({
  email: '',
  password: '',
});
```

### [2. Pass the form model to `form()` to create a `FieldTree`](#2-pass-the-form-model-to-form-to-create-a-fieldtree)

Then, you pass your form model into the [`form()`](/api/forms/signals/form) function to create a **field tree** - an object structure that mirrors your model's shape, allowing you to access fields with dot notation.

Both the root form object and its nested properties are [`FieldTree`](/api/forms/signals/FieldTree) nodes:

```
const loginForm = form(loginModel);

loginForm; // is a FieldTree
loginForm.email; // is also a FieldTree
```

### [3. Bind HTML inputs with `[formField]` directive](#3-bind-html-inputs-with-formfield-directive)

Next, you bind your HTML inputs to the form using the `[formField]` directive, which creates two-way binding between them:

```
<input type="email" [formField]="loginForm.email" />
<input type="password" [formField]="loginForm.password" />
```

As a result, user changes (such as typing in the field) automatically updates the form.

**NOTE:** The `[formField]` directive also syncs field state for attributes like `required`, `disabled`, and `readonly` when appropriate.

### [4. Read state with `FieldTree` signals](#4-read-state-with-fieldtree-signals)

You can access state for any part of the tree by calling the [`FieldTree`](/api/forms/signals/FieldTree) node as a function. This returns a state object containing reactive signals for the value, validation status, and interaction state:

```
loginForm(); // Returns state for the whole form
loginForm.email(); // Returns state for the email field
```

To read the current value, access the `value()` signal:

```
<!-- Render values that update automatically as user types -->
<p>Form value: {{ loginForm().value() | json }}</p>
<p>Email: {{ loginForm.email().value() }}</p>
```

```
// Get the current value
const currentEmail = loginForm.email().value();
```

### [5. Update values with `set()`](#5-update-values-with-set)

You can programmatically update values using the `value.set()` method on any node. This updates both the [`FieldTree`](/api/forms/signals/FieldTree) and the underlying model signal:

```
// Update the value programmatically
loginForm.email().value.set('alice@wonderland.com');
```

As a result, both the field value and the model signal are updated automatically:

```
// The model signal is also updated
console.log(loginModel().email); // 'alice@wonderland.com'
```

### [Complete example](#complete-example)

### app.ts

```
import {Component, signal} from '@angular/core';
import {form, FormField} from '@angular/forms/signals';

interface LoginData {
  email: string;
  password: string;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [FormField],
})
export class App {
  loginModel = signal<LoginData>({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel);

  onSubmit(event: Event) {
    event.preventDefault();

    // Perform login logic here
    const credentials = this.loginModel();
    console.log('Logging in with:', credentials);

    // e.g., await this.authService.login(credentials);
  }
}
```

### app.html

```
<form (submit)="onSubmit($event)">
  <label>
    Email:
    <input type="email" [formField]="loginForm.email" />
  </label>

  <label>
    Password:
    <input type="password" [formField]="loginForm.password" />
  </label>

  <p>Hello {{ loginForm.email().value() }}!</p>
  <p>Password length: {{ loginForm.password().value().length }}</p>

  <button type="submit">Log In</button>
</form>
```

### app.css

```
form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 400px;
  padding: 1rem;
  font-family: Inter, system-ui, -apple-system, sans-serif;
}

label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

input {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
}

p {
  margin: 0.5rem 0;
  color: #666;
}
```

## [Basic usage](#basic-usage)

The `[formField]` directive works with all standard HTML input types. Here are the most common patterns:

### [Text inputs](#text-inputs)

Text inputs work with various `type` attributes and textareas:

```
<!-- Text and email -->
<input type="text" [formField]="form.name" />
<input type="email" [formField]="form.email" />
```

#### [Numbers](#numbers)

Number inputs automatically convert between strings and numbers:

```
<!-- Number - automatically converts to number type -->
<input type="number" [formField]="form.age" />
```

#### [Date and time](#date-and-time)

Date inputs store values as `YYYY-MM-DD` strings, and time inputs use `HH:mm` format:

```
<!-- Date and time - stores as ISO format strings -->
<input type="date" [formField]="form.eventDate" />
<input type="time" [formField]="form.eventTime" />
```

If you need to convert date strings to Date objects, you can do so by passing the field value into `Date()`:

```
const dateObject = new Date(form.eventDate().value());
```

#### [Multiline text](#multiline-text)

Textareas work the same way as text inputs:

```
<!-- Textarea -->
<textarea [formField]="form.message" rows="4"></textarea>
```

### [Checkboxes](#checkboxes)

Checkboxes bind to boolean values:

```
<!-- Single checkbox -->
<label>
  <input type="checkbox" [formField]="form.agreeToTerms" />
  I agree to the terms
</label>
```

#### [Multiple checkboxes](#multiple-checkboxes)

For multiple options, create a separate boolean [`formField`](/api/forms/signals/FormField) for each:

```
<label>
  <input type="checkbox" [formField]="form.emailNotifications" />
  Email notifications
</label>
<label>
  <input type="checkbox" [formField]="form.smsNotifications" />
  SMS notifications
</label>
```

### [Radio buttons](#radio-buttons)

Radio buttons work similarly to checkboxes. As long as the radio buttons use the same `[formField]` value, Signal Forms will automatically bind the same `name` attribute to all of them:

```
<label>
  <input type="radio" value="free" [formField]="form.plan" />
  Free
</label>
<label>
  <input type="radio" value="premium" [formField]="form.plan" />
  Premium
</label>
```

When a user selects a radio button, the form [`formField`](/api/forms/signals/FormField) stores the value from that radio button's `value` attribute. For example, selecting "Premium" sets `form.plan().value()` to `"premium"`.

### [Select dropdowns](#select-dropdowns)

Select elements work with both static and dynamic options:

```
<!-- Static options -->
<select [formField]="form.country">
  <option value="">Select a country</option>
  <option value="us">United States</option>
  <option value="ca">Canada</option>
</select>

<!-- Dynamic options with @for -->
<select [formField]="form.productId">
  <option value="">Select a product</option>
  @for (product of products; track product.id) {
    <option [value]="product.id">{{ product.name }}</option>
  }
</select>
```

**NOTE:** Multiple select (`<select multiple>`) is not supported by the `[formField]` directive at this time.

## [Validation and state](#validation-and-state)

Signal Forms provides built-in validators that you can apply to your form fields. To add validation, pass a schema function as the second argument to [`form()`](/api/forms/signals/form):

```
const loginForm = form(loginModel, (schemaPath) => {
  debounce(schemaPath.email, 500);
  required(schemaPath.email);
  email(schemaPath.email);
});
```

The schema function receives a **schema path** parameter that provides paths to your fields for configuring validation rules.

Common validators include:

* **`required()`** - Ensures the field has a value
* **`email()`** - Validates email format
* **`min()`** / **`max()`** - Validates number ranges
* **[`minLength()`](/api/forms/signals/minLength)** / **[`maxLength()`](/api/forms/signals/maxLength)** - Validates string or collection length
* **`pattern()`** - Validates against a regex pattern

You can also customize error messages by passing an options object as the second argument to the validator:

```
required(schemaPath.email, {message: 'Email is required'});
email(schemaPath.email, {message: 'Please enter a valid email address'});
```

Each node in the [`FieldTree`](/api/forms/signals/FieldTree) exposes its validation and interaction state through reactive signals.

### [FieldTree State Signals](#fieldtree-state-signals)

Every node in the tree, including the root form object, provides the same signals to track its state. Since every node is a [`FieldTree`](/api/forms/signals/FieldTree), the API for monitoring validity and interaction is identical at every level.

| State                                       | Description                                                                     |
| ------------------------------------------- | ------------------------------------------------------------------------------- |
| `valid()`                                   | Returns `true` if the node passes all validation rules                          |
| `invalid()`                                 | Returns `true` if there are validation errors                                   |
| `pending()`                                 | Returns `true` if async validation is in progress                               |
| `touched()`                                 | Returns `true` if the user has focused and blurred the field or any child field |
| `dirty()`                                   | Returns `true` if the value has been changed by the user                        |
| [`disabled()`](/api/forms/signals/disabled) | Returns `true` if the node is disabled                                          |
| [`readonly()`](/api/forms/signals/readonly) | Returns `true` if the node is readonly                                          |
| `errors()`                                  | Returns an array of validation errors with `kind` and `message` properties      |

### [Complete example](#complete-example-validation)

### app.ts

```
import {Component, signal} from '@angular/core';
import {email, form, FormField, required} from '@angular/forms/signals';

interface LoginData {
  email: string;
  password: string;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [FormField],
})
export class App {
  loginModel = signal<LoginData>({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel, (schemaPath) => {
    required(schemaPath.email, {message: 'Email is required'});
    email(schemaPath.email, {message: 'Enter a valid email address'});

    required(schemaPath.password, {message: 'Password is required'});
  });

  onSubmit(event: Event) {
    event.preventDefault();
    // Perform login logic here
    const credentials = this.loginModel();
    console.log('Logging in with:', credentials);
    // e.g., await this.authService.login(credentials);
  }
}
```

### app.html

```
<form (submit)="onSubmit($event)">
  <div>
    <label>
      Email:
      <input type="email" [formField]="loginForm.email" />
    </label>

    @if (loginForm.email().touched() && loginForm.email().invalid()) {
      <ul class="error-list">
        @for (error of loginForm.email().errors(); track error) {
          <li>{{ error.message }}</li>
        }
      </ul>
    }
  </div>

  <div>
    <label>
      Password:
      <input type="password" [formField]="loginForm.password" />
    </label>

    @if (loginForm.password().touched() && loginForm.password().invalid()) {
      <div class="error-list">
        @for (error of loginForm.password().errors(); track error) {
          <p>{{ error.message }}</p>
        }
      </div>
    }
  </div>

  <button type="submit">Log In</button>
</form>
```

### app.css

```
form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 400px;
  padding: 1rem;
  font-family:
    Inter,
    system-ui,
    -apple-system,
    sans-serif;
}

div {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-weight: 500;
}

input {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
}

input:focus {
  outline: none;
  border-color: #4285f4;
}

button {
  padding: 0.75rem 1.5rem;
  background-color: #4285f4;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
  cursor: pointer;
  transition: background-color 0.2s;
}

button:hover {
  background-color: #357ae8;
}

button:active {
  background-color: #2a65c8;
}

.error-list {
  color: red;
  font-size: 0.875rem;
  margin: 0.25rem 0 0 0;
  padding-left: 0;
  list-style-position: inside;
}

.error-list p {
  margin: 0;
}
```

## [Next steps](#next-steps)

To learn more about Signal Forms and how it works, check out the in-depth guides:

* [Overview](guide/forms/signals/overview) - Introduction to Signal Forms and when to use them
* [Form models](guide/forms/signals/models) - Creating and managing form data with signals
* [Field state management](guide/forms/signals/field-state-management) - Working with validation state, interaction tracking, and field visibility
* [Validation](guide/forms/signals/validation) - Built-in validators, custom validation rules, and async validation

----
url: https://angular.dev/errors/NG0991
----

This error happens when an `Observable`-backed resource — [`rxResource`](/ecosystem/rxjs-interop#using-rxresource-for-async-data) or [`httpResource`](/api/common/http/httpResource) — finishes (completes) without ever sending a value or an error.

A resource always needs to end up with *something*: a value it loaded, or an error explaining what went wrong. An Observable that just completes silently doesn't give Angular either of those, so Angular doesn't know what to put in the resource — and it throws this error instead.

## [With `httpResource`](/errors/NG0991#with-httpresource)

[`httpResource`](/api/common/http/httpResource) doesn't give you an Observable to write yourself — it makes the HTTP call internally through [`HttpClient`](/api/common/http/HttpClient) and completes once the response comes back. So if you're seeing this error from [`httpResource`](/api/common/http/httpResource), the request itself isn't the problem; something sitting in front of it is swallowing the response before it gets there.

The usual suspect is an [`HttpInterceptor`](/api/common/http/HttpInterceptor) that catches an error and doesn't re-emit anything, the same `EMPTY` mistake as below but living in an interceptor instead of your own code:

```
@Injectable()
class MyInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<unknown>, next: HttpHandler) {
    return next.handle(req).pipe(
      // BAD: same problem as `catchError(() => EMPTY)` in a `stream` —
      // the request completes with no response and no error, and any
      // httpResource depending on it hits NG0991.
      catchError(() => EMPTY),
    );
  }
}
```

Fix it the same way as you would in an interceptor pipeline generally: let the error propagate (drop the `catchError`), or replace it with something that actually resolves the request, like `of(new HttpResponse({body: fallbackValue}))`.

## [With `rxResource`](/errors/NG0991#with-rxresource)

The most common way to accidentally cause this is catching an error and swallowing it with `EMPTY`:

```
import {EMPTY, catchError} from 'rxjs';

const userResource = rxResource({
  params: () => ({id: userId()}),
  stream: ({params}) =>
    this.http.get(`/users/${params.id}`).pipe(
      // BAD: this hides the error completely. The Observable just
      // completes with nothing, and the resource has no value and no
      // error to show for it — this is what triggers NG0991.
      catchError(() => EMPTY),
    ),
});
```

Another common way this happens: combining several Observables (with `merge`, `race`, and similar) where one of them can complete on its own before ever emitting, without a `startWith(...)` to guarantee an initial value. If none of the combined sources ever emits before they've all completed, the combined stream completes empty too.

Pick whichever fix makes sense for your case:

**Let the error through.** If you don't need to handle the error yourself, just remove the `catchError` — the resource will end up in its `error` state, which you can already check with `userResource.error()`.

```
stream: ({params}) => this.http.get(`/users/${params.id}`),
```

**Recover with a fallback value.** If you'd rather show something instead of an error, use `catchError` to return an Observable that emits a value, such as `of(...)`:

```
import {of, catchError} from 'rxjs';

stream: ({params}) =>
  this.http.get(`/users/${params.id}`).pipe(
    catchError(() => of(null)), // resource resolves with `null` instead of erroring
  ),
```

Either way, make sure the Observable you return from `stream` always emits at least one value or an error before it completes.

## [Where you'll actually see this error](/errors/NG0991#where-youll-actually-see-this-error)

The error isn't thrown when the resource is created, and not necessarily where the request completes either. It's thrown the next time something reads the resource's value — for example `userResource.value()` in a template binding. That can make it look like the bug is somewhere else in your app, when the real cause is upstream (the `stream` completing empty, or an interceptor swallowing the response).

If nothing in your code checks the resource's status before reading `.value()`, this error can bubble all the way up to Angular's global [`ErrorHandler`](/api/core/ErrorHandler), the same as any other uncaught error thrown while rendering a template.

Two ways to reduce the impact of this, from most to least preferred:

1. **Fix it at the root**, using one of the approaches above, so the resource never ends up in this state.
2. **Guard reads of `.value()`** as defense in depth, in case something you don't fully control (a third-party interceptor or service, for example) ever completes empty unexpectedly. Check `.hasValue()` or `.status()` before reading `.value()`, for example with `@if (userResource.hasValue()) { {{ userResource.value() }} }`, instead of reading `.value()` unconditionally.

----
url: https://angular.dev/api/aria/grid/GRID
----

```
    
      const GRID: InjectionToken<Grid>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/WithFieldTree
----

```
    
      type WithFieldTree<T> = T & {fieldTree: ReadonlyFieldTree<unknown>}
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/Meta
----

```
    
      class Meta {}
    
    
```

Retrieves or creates a specific `<meta>` tag element in the current HTML document. In searching for an existing tag, Angular attempts to match the `name` or `property` attribute values in the provided tag definition, and verifies that all other attribute values are equal. If an existing element is found, it is returned and is not modified in any way.

@paramtag`MetaDefinition`

The definition of a `<meta>` element to match or create.

@paramforceCreation`boolean`

True to create a new element without checking whether one already exists.

@returns`HTMLMetaElement | null`

The existing element with the same attributes and values if found, the new element if no match is found, or `null` if the tag parameter is not defined.

Retrieves or creates a set of `<meta>` tag elements in the current HTML document. In searching for an existing tag, Angular attempts to match the `name` or `property` attribute values in the provided tag definition, and verifies that all other attribute values are equal.

@paramtags`MetaDefinition[]`

An array of tag definitions to match or create.

@paramforceCreation`boolean`

True to create new elements without checking whether they already exist.

@returns`HTMLMetaElement[]`

The matching elements if found, or the new elements.

Retrieves a `<meta>` tag element in the current HTML document.

@paramattrSelector`string`

The tag attribute and value to match against, in the format `"tag_attribute='value string'"`.

@returns`HTMLMetaElement | null`

The matching element, if any.

Retrieves a set of `<meta>` tag elements in the current HTML document.

@paramattrSelector`string`

The tag attribute and value to match against, in the format `"tag_attribute='value string'"`.

@returns`HTMLMetaElement[]`

The matching elements, if any.

Modifies an existing `<meta>` tag element in the current HTML document.

@paramtag`MetaDefinition`

The tag description with which to replace the existing tag content.

@paramselector`string | undefined`

A tag attribute and value to match against, to identify an existing tag. A string in the format `"tag_attribute=`value string`"`. If not supplied, matches a tag with the same `name` or `property` attribute value as the replacement tag.

@returns`HTMLMetaElement | null`

Removes an existing `<meta>` tag element from the current HTML document.

@paramattrSelector`string`

A tag attribute and value to match against, to identify an existing tag. A string in the format `"tag_attribute=`value string`"`.

@returns`void`

Removes an existing `<meta>` tag element from the current HTML document.

@parammeta`HTMLMetaElement`

The tag definition to match against to identify an existing tag.

@returns`void`

## [Description](#description)

A service for managing HTML `<meta>` tags.

Properties of the [`MetaDefinition`](/api/platform-browser/MetaDefinition) object match the attributes of the HTML `<meta>` tag. These tags define document metadata that is important for things like configuring a Content Security Policy, defining browser compatibility and security settings, setting HTTP Headers, defining rich content for social sharing, and Search Engine Optimization (SEO).

To identify specific `<meta>` tags in a document, use an attribute selection string in the format `"tag_attribute='value string'"`. For example, an `attrSelector` value of `"name='description'"` matches a tag whose `name` attribute has the value `"description"`. Selectors are used with the `querySelector()` Document method, in the format `meta[{attrSelector}]`.

Jump to details

----
url: https://angular.dev/events/v22
----

Angular v22 is ready for you to build modern, high-performance web applications. This release introduces key stabilization updates, template enhancements, and API improvements:

* **Stabilized APIs**: Signal Forms, Asynchronous Signals, and Angular Aria are now stable, offering a production-ready reactive foundation.
* **Template enhancements**: New template features streamline development, improve ergonomics, and enhance code clarity.
* **API improvements**: Core APIs have been updated for better performance, simpler syntax, and more robust typing.
* **Angular AI integration**: Streamlined support and updates for AI-driven development workflows.

## [Key resources](#key-resources)

### [v22 Release Blog](https://goo.gle/angular-v22-blog)

[Discover all the major features, community updates, and design decisions behind Angular v22.](https://goo.gle/angular-v22-blog)

[Read the blog](https://goo.gle/angular-v22-blog)

### [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md#2200-2026-06-03)

[Review the detailed, commit-by-commit list of new features, bug fixes, and breaking changes.](https://github.com/angular/angular/blob/main/CHANGELOG.md#2200-2026-06-03)

[View the changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md#2200-2026-06-03)

### [Update to v22](https://angular.dev/update-guide?v=21.0-22.0\&l=3)

[Get instructions on how to update your project to Angular v22.](https://angular.dev/update-guide?v=21.0-22.0\&l=3)

[Chart your update path to v22](https://angular.dev/update-guide?v=21.0-22.0\&l=3)

## [Developer event](#developer-event)

[Watch the Angular v22 Developer Event online](https://goo.gle/angular-v22-yt).

[](https://www.youtube.com/watch?v=h5OJUSS_8IA\&autoplay=1)

----
url: https://angular.dev/llms.txt
----

```
# Angular

Angular — Deliver web apps with confidence 🚀

## Table of Contents

- [What is Angular](https://angular.dev/overview)
- [Installation guide](https://angular.dev/installation)
- [Style Guide](https://next.angular.dev/style-guide)

## Components

- [What is a component](https://angular.dev/guide/components)
- [Component selectors](https://angular.dev/guide/components/selectors)
- [Styling components](https://angular.dev/guide/components/styling)
- [Accepting data with input properties](https://angular.dev/guide/components/inputs)
- [Custom events with output](https://angular.dev/guide/components/outputs)
- [Content projection](https://angular.dev/guide/components/content-projection)
- [Component lifecycle](https://angular.dev/guide/components/lifecycle)

## Templates guides

- [Template Overview](https://angular.dev/guide/templates)
- [Adding event listeners](https://angular.dev/guide/templates/event-listeners)
- [Binding text, properties and attributes](https://angular.dev/guide/templates/binding)
- [Control Flow](https://angular.dev/guide/templates/control-flow)
- [Template variable declaration](https://angular.dev/guide/templates/variables)
- [Deferred loading of components](https://angular.dev/guide/templates/defer) 
- [Expression syntax](https://angular.dev/guide/templates/expression-syntax)

## Directives

- [Directives overview](https://angular.dev/guide/directives)
- [Attribute directives](https://angular.dev/guide/directives/attribute-directives)
- [Structural directives](https://angular.dev/guide/directives/structural-directives)
- [Directive composition](https://angular.dev/guide/directives/directive-composition-api)
- [Optimizing images](https://angular.dev/guide/image-optimization)

## Signals 

- [Signals overview](https://angular.dev/guide/signals)
- [Dependent state with linkedSignal](https://angular.dev/guide/signals/linked-signal)
- [Async reactivity with resources](https://angular.dev/guide/signals/resource)

## Dependency injection (DI)

- [Dependency Injection overview](https://angular.dev/guide/di)
- [Creating and using services](https://angular.dev/guide/di/creating-and-using-services)
- [Defining dependency providers](https://angular.dev/guide/di/defining-dependency-providers)
- [Lazy loading services](https://angular.dev/guide/di/lazy-loading-services)
- [Injection context](https://angular.dev/guide/di/dependency-injection-context)
- [Hierarchical injectors](https://angular.dev/guide/di/hierarchical-dependency-injection)
- [Optimizing Injection tokens](https://angular.dev/guide/di/lightweight-injection-tokens)

## RxJS 

- [RxJS interop with Angular signals](https://angular.dev/ecosystem/rxjs-interop)
- [Component output interop](https://angular.dev/ecosystem/rxjs-interop/output-interop)

## Loading Data

- [HttpClient overview](https://angular.dev/guide/http)
- [Setting up the HttpClient](https://angular.dev/guide/http/setup)
- [Making requests](https://angular.dev/guide/http/making-requests)
- [Intercepting requests](https://angular.dev/guide/http/interceptors)
- [Reactive data fetching with httpResource](https://angular.dev/guide/http/http-resource)
- [Testing](https://angular.dev/guide/http/testing)

## Forms
- [Forms overview](https://angular.dev/guide/forms)
- [Reactive Forms](https://angular.dev/guide/forms/reactive-forms)
- [Strictly types forms](https://angular.dev/guide/forms/typed-forms)
- [Template driven forms](https://angular.dev/guide/forms/template-driven-forms)
- [Validate forms input](https://angular.dev/guide/forms/form-validation)
- [Building dynamic forms](https://angular.dev/guide/forms/dynamic-forms)
- [Signal Forms](https://angular.dev/guide/forms/signals/overview)

## Accessibility
- [Angular Aria overview](https://angular.dev/guide/aria/overview)

## Routing
- [Routing overview](https://angular.dev/guide/routing)
- [Define routes](https://angular.dev/guide/routing/define-routes)
- [Route loading strategies](https://angular.dev/guide/routing/loading-strategies)
- [Show routes with outlets](https://angular.dev/guide/routing/show-routes-with-outlets)
- [Navigate to routes](https://angular.dev/guide/routing/navigate-to-routes)
- [Read route state](https://angular.dev/guide/routing/read-route-state)
- [Common routing tasks](https://angular.dev/guide/routing/common-router-tasks)
- [Creating custom route matches](https://angular.dev/guide/routing/routing-with-urlmatcher)

## Server Side Rendering (SSR)

- [SSR Overview](https://angular.dev/guide/performance)
- [Server and hybrid rendering](https://angular.dev/guide/ssr)
- [Rendering strategies in Angular](https://angular.dev/guide/routing/rendering-strategies)
- [Hydration](https://angular.dev/guide/hydration)
- [Incremental Hydration](https://angular.dev/guide/incremental-hydration)

# CLI 
[Angular CLI Overview](https://angular.dev/tools/cli)

## Testing

- [Testing overview](https://angular.dev/guide/testing)
- [Testing coverage](https://angular.dev/guide/testing/code-coverage)
- [Testing services](https://angular.dev/guide/testing/services)
- [Basics of component testing](https://angular.dev/guide/testing/components-basics)
- [Component testing scenarios](https://angular.dev/guide/testing/components-scenarios)
- [Testing attribute directives](https://angular.dev/guide/testing/attribute-directives)
- [Testing pipes](https://angular.dev/guide/testing/pipes)
- [Debugging tests](https://angular.dev/guide/testing/debugging)
- [Testing utility apis](https://angular.dev/guide/testing/utility-apis)
- [Component harness overview](https://angular.dev/guide/testing/component-harnesses-overview)
- [Using component harness in tests](https://angular.dev/guide/testing/using-component-harnesses)
- [Creating a component harness for your components](https://angular.dev/guide/testing/creating-component-harnesses)

## Animations
- [Animations your content](https://angular.dev/guide/animations/css)
- [Route transition animation](https://angular.dev/guide/routing/route-transition-animations)
- [Migrating to native CSS animations](https://next.angular.dev/guide/animations/migration)

## APIs
- [API reference](https://angular.dev/api)
- [CLI command reference](https://angular.dev/cli)


## Others

- [Zoneless](https://angular.dev/guide/zoneless)
- [Error encyclopedia](https://angular.dev/errors)
- [Extended diagnostics](https://angular.dev/extended-diagnostics)
- [Update guide](https://angular.dev/update-guide)
- [Contribute to Angular](https://github.com/angular/angular/blob/main/CONTRIBUTING.md)
- [Angular's Roadmap](https://angular.dev/roadmap)
- [Keeping your projects up-to-date](https://angular.dev/update)
- [Security](https://angular.dev/best-practices/security)
- [Internationalization (i18n)](https://angular.dev/guide/i18n)
```

----
url: https://angular.dev/api/forms/signals/MaybeFieldTree
----

```
    
      type MaybeFieldTree<TModel, TKey extends string | number = string | number, TMode extends 'writable' | 'readonly' = 'writable'> = (TModel & undefined) | FieldTree<Exclude<TModel, undefined>, TKey, TMode>
    
    
```

## [Description](#description)

Helper type for defining [`FieldTree`](/api/forms/signals/FieldTree). Given a type `TValue` that may include `undefined`, it extracts the `undefined` outside the [`FieldTree`](/api/forms/signals/FieldTree) type.

For example `MaybeFieldTree<{a: number} | undefined, TKey>` would be equivalent to `undefined | FieldTree<{a: number}, TKey>`.

Jump to details

----
url: https://angular.dev/api/service-worker/VersionInstallationFailedEvent
----

```
    
      interface VersionInstallationFailedEvent {}
    
    
```

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/getting-started
----

This document explains how to enable Angular service worker support in projects that you created with the [Angular CLI](tools/cli). It then uses an example to show you a service worker in action, demonstrating loading and basic caching.

## [Adding a service worker to your project](#adding-a-service-worker-to-your-project)

To set up the Angular service worker in your project, run the following CLI command:

```
ng add @angular/pwa
```

The CLI configures your application to use service workers with the following actions:

1. Adds the `@angular/service-worker` package to your project.

2. Enables service worker build support in the CLI.

3. Imports and registers the service worker with the application's root providers.

4. Updates the `index.html` file:

   * Includes a link to add the `manifest.webmanifest` file
   * Adds a meta tag for `theme-color`

5. Installs icon files to support the installed Progressive Web App (PWA).

6. Creates the service worker configuration file called [`ngsw-config.json`](ecosystem/service-workers/config), which specifies the caching behaviors and other settings.

Now, build the project:

```
ng build
```

The CLI project is now set up to use the Angular service worker.

## [Service worker in action: a tour](#service-worker-in-action-a-tour)

This section demonstrates a service worker in action, using an example application. To enable service worker support during local development, use the production configuration with the following command:

```
ng serve --configuration=production
```

Alternatively, you can use the [`http-server package`](https://www.npmjs.com/package/http-server) from npm, which supports service worker applications. Run it without installation using:

```
npx http-server -p 8080 -c-1 dist/<project-name>/browser
```

This will serve your application with service worker support at <http://localhost:8080>.

### [Initial load](#initial-load)

With the server running on port `8080`, point your browser at `http://localhost:8080`. Your application should load normally.

**TIP:** When testing Angular service workers, it's a good idea to use an incognito or private window in your browser to ensure the service worker doesn't end up reading from a previous leftover state, which can cause unexpected behavior.

**HELPFUL:** If you are not using HTTPS, the service worker will only be registered when accessing the application on `localhost`.

### [Simulating a network issue](#simulating-a-network-issue)

To simulate a network issue, disable network interaction for your application.

In Chrome:

1. Select **Tools** > **Developer Tools** (from the Chrome menu located in the top right corner).
2. Go to the **Network tab**.
3. Select **Offline** in the **Throttling** dropdown menu.

Now the application has no access to network interaction.

For applications that do not use the Angular service worker, refreshing now would display Chrome's Internet disconnected page that says "There is no Internet connection".

With the addition of an Angular service worker, the application behavior changes. On a refresh, the page loads normally.

Look at the Network tab to verify that the service worker is active.

**HELPFUL:** Under the "Size" column, the requests state is `(ServiceWorker)`. This means that the resources are not being loaded from the network. Instead, they are being loaded from the service worker's cache.

### [What's being cached?](#whats-being-cached)

Notice that all of the files the browser needs to render this application are cached. The `ngsw-config.json` boilerplate configuration is set up to cache the specific resources used by the CLI:

* `index.html`
* `favicon.ico`
* Build artifacts (JS and CSS bundles)
* Anything under `assets`
* Images and fonts directly under the configured `outputPath` (by default `./dist/<project-name>/`) or `resourcesOutputPath`. See the documentation for `ng build` for more information about these options.

**IMPORTANT:** The generated `ngsw-config.json` includes a limited list of cacheable fonts and images extensions. In some cases, you might want to modify the glob pattern to suit your needs.

**IMPORTANT:** If `resourcesOutputPath` or `assets` paths are modified after the generation of configuration file, you need to change the paths manually in `ngsw-config.json`.

### [Making changes to your application](#making-changes-to-your-application)

Now that you've seen how service workers cache your application, the next step is understanding how updates work. Make a change to the application, and watch the service worker install the update:

1. If you're testing in an incognito window, open a second blank tab. This keeps the incognito and the cache state alive during your test.

2. Close the application tab, but not the window. This should also close the Developer Tools.

3. Shut down `http-server` (Ctrl-c).

4. Open `src/app/app.component.html` for editing.

5. Change the text `Welcome to {{title}}!` to `Bienvenue à {{title}}!`.

6. Build and run the server again:

```
ng build
npx http-server -p 8080 -c-1 dist/<project-name>/browser
```

### [Updating your application in the browser](#updating-your-application-in-the-browser)

Now look at how the browser and service worker handle the updated application.

1. Open <http://localhost:8080> again in the same window. What happens?

   What went wrong? *Nothing, actually!* The Angular service worker is doing its job and serving the version of the application that it has **installed**, even though there is an update available. In the interest of speed, the service worker doesn't wait to check for updates before it serves the application that it has cached.

   Look at the `http-server` logs to see the service worker requesting `/ngsw.json`.

   ```
   [2023-09-07T00:37:24.372Z]  "GET /ngsw.json?ngsw-cache-bust=0.9365263935102124" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
   ```

   This is how the service worker checks for updates.

2. Refresh the page.

   The service worker installed the updated version of your application *in the background*, and the next time the page is loaded or reloaded, the service worker switches to the latest version.

## [Service worker configuration](#service-worker-configuration)

Angular service workers support comprehensive configuration options through the [`SwRegistrationOptions`](/api/service-worker/SwRegistrationOptions) interface, providing fine-grained control over registration behavior, caching, and script execution.

### [Enabling and disabling service workers](#enabling-and-disabling-service-workers)

The `enabled` option controls whether the service worker will be registered and related services will attempt to communicate with it.

```
import {ApplicationConfig, isDevMode} from '@angular/core';
import {provideServiceWorker} from '@angular/service-worker';

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(), // Disable in development, enable in production
    }),
  ],
};
```

### [Cache control with updateViaCache](#cache-control-with-updateviacache)

The `updateViaCache` option controls how the browser consults the HTTP cache during service worker updates. This provides fine-grained control over when the browser fetches updated service worker scripts and imported modules.

```
export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      updateViaCache: 'imports',
    }),
  ],
};
```

The `updateViaCache` option accepts the following values:

* **`'imports'`** - The HTTP cache is consulted for the service worker script's imported scripts, but not for the service worker script itself
* **`'all'`** - The HTTP cache is consulted for both the service worker script and its imported scripts
* **`'none'`** - The HTTP cache is not consulted for the service worker script or its imported scripts

### [ES Module support with type option](#es-module-support-with-type-option)

The `type` option enables specifying the script type when registering service workers, providing support for ES module features in your service worker scripts.

```
export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      type: 'module', // Enable ES module features
    }),
  ],
};
```

The `type` option accepts the following values:

* **`'classic'`** (default) - Traditional service worker script execution. ES module features such as `import` and `export` are NOT allowed in the script
* **`'module'`** - Registers the script as an ES module. Allows use of `import`/`export` syntax and module features

### [Registration scope control](#registration-scope-control)

The `scope` option defines the service worker's registration scope, determining what range of URLs it can control.

```
export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      scope: '/app/', // Service worker will only control URLs under /app/
    }),
  ],
};
```

* Controls which URLs the service worker can intercept and manage
* By default, the scope is the directory containing the service worker script
* Used when calling `ServiceWorkerContainer.register()`

### [Registration strategy configuration](#registration-strategy-configuration)

The `registrationStrategy` option defines when the service worker will be registered with the browser, providing control over the timing of registration.

```
export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
};
```

Available registration strategies:

* **`'registerWhenStable:timeout'`** (default: `'registerWhenStable:30000'`) - Register as soon as the application stabilizes (no pending micro-/macro-tasks) but no later than the specified timeout in milliseconds The timeout is required. Without it, `'registerWhenStable'` registers the service worker immediately.
* **`'registerImmediately'`** - Register the service worker immediately
* **`'registerWithDelay:timeout'`** - Register with a delay of the specified timeout in milliseconds

```
// Register immediately
export const immediateConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerImmediately',
    }),
  ],
};

// Register with a 5-second delay
export const delayedConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWithDelay:5000',
    }),
  ],
};
```

You can also provide an Observable factory function for custom registration timing:

```
import {timer} from 'rxjs';

export const customConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('ngsw-worker.js', {
      enabled: !isDevMode(),
      registrationStrategy: () => timer(10_000), // Register after 10 seconds
    }),
  ],
};
```

## [More on Angular service workers](#more-on-angular-service-workers)

You might also be interested in the following:

----
url: https://angular.dev/guide/forms/form-validation
----

You can improve overall data quality by validating user input for accuracy and completeness. This page shows how to validate user input from the UI and display useful validation messages, in both reactive and template-driven forms.

## [Validating input in template-driven forms](#validating-input-in-template-driven-forms)

To add validation to a template-driven form, you add the same validation attributes as you would with [native HTML form validation](https://developer.mozilla.org/docs/Web/Guide/HTML/HTML5/Constraint_validation). Angular uses directives to match these attributes with validator functions in the framework.

Every time the value of a form control changes, Angular runs validation and generates either a list of validation errors that results in an `INVALID` status, or `null`, which results in a `VALID` status.

You can then inspect the control's state by exporting [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) to a local template variable. The following example exports [`NgModel`](/api/forms/NgModel) into a variable called `name`:

### actor-form-template.component.html (name)

```
<input
  type="text"
  id="name"
  name="name"
  class="form-control"
  required
  minlength="4"
  appForbiddenName="bob"
  [(ngModel)]="actor.name"
  #name="ngModel"
/>

@if (name.invalid && (name.dirty || name.touched)) {
  <div class="alert">
    @if (name.hasError('required')) {
      <div>Name is required.</div>
    }
    @if (name.hasError('minlength')) {
      <div>Name must be at least 4 characters long.</div>
    }
    @if (name.hasError('forbiddenName')) {
      <div>Name cannot be Bob.</div>
    }
  </div>
}
```

Notice the following features illustrated by the example.

* The `<input>` element carries the HTML validation attributes: `required` and [`minlength`](/api/forms/MinLengthValidator). It also carries a custom validator directive, `forbiddenName`. For more information, see the [Custom validators](#defining-custom-validators) section.

* `#name="ngModel"` exports [`NgModel`](/api/forms/NgModel) into a local variable called `name`. [`NgModel`](/api/forms/NgModel) mirrors many of the properties of its underlying [`FormControl`](/api/forms/FormControl) instance, so you can use this in the template to check for control states such as `valid` and `dirty`. For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl) API reference.

  * The outermost `@if` reveals a set of nested messages but only if the `name` is invalid and the control is either `dirty` or `touched`.

  * Each nested `@if` can present a custom message for one of the possible validation errors. There are messages for `required`, [`minlength`](/api/forms/MinLengthValidator), and `forbiddenName`.

**HELPFUL:** To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the `dirty` or `touched` states in a control.

* When the user changes the value in the watched field, the control is marked as "dirty"
* When the user blurs the form control element, the control is marked as "touched"

## [Validating input in reactive forms](#validating-input-in-reactive-forms)

In a reactive form, the source of truth is the component class. Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class. Angular then calls these functions whenever the value of the control changes.

### [Validator functions](#validator-functions)

Validator functions can be either synchronous or asynchronous.

| Validator type   | Details                                                                                                                                                                                                                                           |
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Sync validators  | Synchronous functions that take a control instance and immediately return either a set of validation errors or `null`. Pass these in as the second argument when you instantiate a [`FormControl`](/api/forms/FormControl).                       |
| Async validators | Asynchronous functions that take a control instance and return a Promise or Observable that later emits a set of validation errors or `null`. Pass these in as the third argument when you instantiate a [`FormControl`](/api/forms/FormControl). |

For performance reasons, Angular only runs async validators if all sync validators pass. Each must complete before errors are set.

### [Built-in validator functions](#built-in-validator-functions)

You can choose to [write your own validator functions](#defining-custom-validators), or you can use some of Angular's built-in validators.

The same built-in validators that are available as attributes in template-driven forms, such as `required` and [`minlength`](/api/forms/MinLengthValidator), are all available to use as functions from the [`Validators`](/api/forms/Validators) class. For a full list of built-in validators, see the [Validators](api/forms/Validators) API reference.

To update the actor form to be a reactive form, use some of the same built-in validators —this time, in function form, as in the following example.

### actor-form-reactive.component.ts (validator functions)

```
actorForm = new FormGroup({
  name: new FormControl(this.actor.name, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i), // <-- Here's how you pass in the custom validator.
  ]),
  role: new FormControl(this.actor.role),
  skill: new FormControl(this.actor.skill, Validators.required),
});

get name() {
  return this.actorForm.get('name');
}

get skill() {
  return this.actorForm.get('skill');
}
```

In this example, the `name` control sets up two built-in validators —[`Validators.required`](/api/forms/Validators#required) and [`Validators.minLength(4)`](/api/forms/Validators#minLength)— and one custom validator, `forbiddenNameValidator`.

All of these validators are synchronous, so they are passed as the second argument. Notice that you can support multiple validators by passing the functions in as an array.

This example also adds a few getter methods. In a reactive form, you can always access any form control through the `get` method on its parent group, but sometimes it's useful to define getters as shorthand for the template.

If you look at the template for the `name` input again, it is fairly similar to the template-driven example.

### actor-form-reactive.component.html (name with error msg)

```
<input type="text" id="name" class="form-control" formControlName="name" required />

@if (name.invalid && (name.dirty || name.touched)) {
  <div class="alert alert-danger">
    @if (name.hasError('required')) {
      <div>Name is required.</div>
    }
    @if (name.hasError('minlength')) {
      <div>Name must be at least 4 characters long.</div>
    }
    @if (name.hasError('forbiddenName')) {
      <div>Name cannot be Bob.</div>
    }
  </div>
}
```

This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class.

Notice that the `required` attribute is still present in the template. Although it's not necessary for validation, it should be retained for accessibility purposes.

## [Defining custom validators](#defining-custom-validators)

The built-in validators don't always match the exact use case of your application, so you sometimes need to create a custom validator.

Consider the `forbiddenNameValidator` function from the previous example. Here's what the definition of that function looks like.

### forbidden-name.directive.ts (forbiddenNameValidator)

```
/** An actor's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}
```

The function is a factory that takes a regular expression to detect a *specific* forbidden name and returns a validator function.

In this sample, the forbidden name is "bob", so the validator rejects any actor name containing "bob". Elsewhere it could reject "alice" or any name that the configuring regular expression matches.

The `forbiddenNameValidator` factory returns the configured validator function. That function takes an Angular control object and returns *either* null if the control value is valid *or* a validation error object. The validation error object typically has a property whose name is the validation key, `'forbiddenName'`, and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.

Custom async validators are similar to sync validators, but they must instead return a Promise or observable that later emits null or a validation error object. In the case of an observable, the observable must complete, at which point the form uses the last value emitted for validation.

### [Adding custom validators to reactive forms](#adding-custom-validators-to-reactive-forms)

In reactive forms, add a custom validator by passing the function directly to the [`FormControl`](/api/forms/FormControl).

### actor-form-reactive.component.ts (validator functions)

```
actorForm = new FormGroup({
  name: new FormControl(this.actor.name, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i), // <-- Here's how you pass in the custom validator.
  ]),
  role: new FormControl(this.actor.role),
  skill: new FormControl(this.actor.skill, Validators.required),
});
```

### [Adding custom validators to template-driven forms](#adding-custom-validators-to-template-driven-forms)

In template-driven forms, add a directive to the template, where the directive wraps the validator function. For example, the corresponding `ForbiddenValidatorDirective` serves as a wrapper around the `forbiddenNameValidator`.

Angular recognizes the directive's role in the validation process because the directive registers itself with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) provider, as shown in the following example. [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) is a predefined provider with an extensible collection of validators.

### forbidden-name.directive.ts (providers)

```
providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => ForbiddenValidatorDirective),
      multi: true,
    },
  ],
})
export class ForbiddenValidatorDirective implements Validator {
  readonly forbiddenName = input<string>('', {alias: 'appForbiddenName'});

  validate(control: AbstractControl): ValidationErrors | null {
    return this.forbiddenName()
      ? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control)
      : null;
  }
}
```

The directive class then implements the `Validator` interface, so that it can easily integrate with Angular forms. Here is the rest of the directive to help you get an idea of how it all comes together.

### forbidden-name.directive.ts (directive)

```
@Directive({
  selector: '[appForbiddenName]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => ForbiddenValidatorDirective),
      multi: true,
    },
  ],
})
export class ForbiddenValidatorDirective implements Validator {
  readonly forbiddenName = input<string>('', {alias: 'appForbiddenName'});

  validate(control: AbstractControl): ValidationErrors | null {
    return this.forbiddenName()
      ? forbiddenNameValidator(new RegExp(this.forbiddenName(), 'i'))(control)
      : null;
  }
}
```

Once the `ForbiddenValidatorDirective` is ready, you can add its selector, `appForbiddenName`, to any input element to activate it. For example:

### actor-form-template.component.html (forbidden-name-input)

```
<input
  type="text"
  id="name"
  name="name"
  class="form-control"
  required
  minlength="4"
  appForbiddenName="bob"
  [(ngModel)]="actor.name"
  #name="ngModel"
/>
```

**HELPFUL:** Notice that the custom validation directive is instantiated with `useExisting` rather than `useClass`. The registered validator must be *this instance* of the `ForbiddenValidatorDirective` —the instance in the form with its `forbiddenName` property bound to "bob".

If you were to replace `useExisting` with `useClass`, then you'd be registering a new class instance, one that doesn't have a `forbiddenName`.

## [Control status CSS classes](#control-status-css-classes)

Angular automatically mirrors many control properties onto the form control element as CSS classes. Use these classes to style form control elements according to the state of the form. The following classes are currently supported.

* `.ng-valid`
* `.ng-invalid`
* `.ng-pending`
* `.ng-pristine`
* `.ng-dirty`
* `.ng-untouched`
* `.ng-touched`
* `.ng-submitted` (enclosing form element only)

In the following example, the actor form uses the `.ng-valid` and `.ng-invalid` classes to set the color of each form control's border.

### forms.css (status classes)

```
.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}

.alert div {
  background-color: #fed3d3;
  color: #820000;
  padding: 1rem;
  margin-bottom: 1rem;
}

.form-group {
  margin-bottom: 1rem;
}

label {
  display: block;
  margin-bottom: .5rem;
}

select {
  width: 100%;
  padding: .5rem;
}
```

## [Cross-field validation](#cross-field-validation)

A cross-field validator is a [custom validator](#defining-custom-validators "Read about custom validators") that compares the values of different fields in a form and accepts or rejects them in combination. For example, you might have a form that offers mutually incompatible options, so that if the user can choose A or B, but not both. Some field values might also depend on others; a user might be allowed to choose B only if A is also chosen.

The following cross validation examples show how to do the following:

* Validate reactive or template-based form input based on the values of two sibling controls,
* Show a descriptive error message after the user interacted with the form and the validation failed.

The examples use cross-validation to ensure that actors do not reuse the same name in their role by filling out the Actor Form. The validators do this by checking that the actor names and roles do not match.

### [Adding cross-validation to reactive forms](#adding-cross-validation-to-reactive-forms)

The form has the following structure:

```
const actorForm = new FormGroup({
  'name': new FormControl(),
  'role': new FormControl(),
  'skill': new FormControl(),
});
```

Notice that the `name` and `role` are sibling controls. To evaluate both controls in a single custom validator, you must perform the validation in a common ancestor control: the [`FormGroup`](/api/forms/FormGroup). You query the [`FormGroup`](/api/forms/FormGroup) for its child controls so that you can compare their values.

To add a validator to the [`FormGroup`](/api/forms/FormGroup), pass the new validator in as the second argument on creation.

```
const actorForm = new FormGroup(
  {
    'name': new FormControl(),
    'role': new FormControl(),
    'skill': new FormControl(),
  },
  {validators: unambiguousRoleValidator},
);
```

The validator code is as follows.

### unambiguous-role.directive.ts

```
/** An actor's name can't match the actor's role */
export const unambiguousRoleValidator: ValidatorFn = (
  control: AbstractControl,
): ValidationErrors | null => {
  const name = control.get('name');
  const role = control.get('role');

  return name && role && name.value === role.value ? {unambiguousRole: true} : null;
};
```

The `unambiguousRoleValidator` validator implements the [`ValidatorFn`](/api/forms/ValidatorFn) interface. It takes an Angular control object as an argument and returns either null if the form is valid, or [`ValidationErrors`](/api/forms/ValidationErrors) otherwise.

The validator retrieves the child controls by calling the [`FormGroup`](/api/forms/FormGroup)'s [get](api/forms/AbstractControl#get) method, then compares the values of the `name` and `role` controls.

If the values do not match, the role is unambiguous, both are valid, and the validator returns null. If they do match, the actor's role is ambiguous and the validator must mark the form as invalid by returning an error object.

To provide better user experience, the template shows an appropriate error message when the form is invalid.

### actor-form-reactive.component.html

```
@if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) {
  <div class="cross-validation-error-message alert alert-danger">
    Name cannot match role or audiences will be confused.
  </div>
}
```

This `@if` displays the error if the [`FormGroup`](/api/forms/FormGroup) has the cross validation error returned by the `unambiguousRoleValidator` validator, but only if the user finished [interacting with the form](#control-status-css-classes).

### [Adding cross-validation to template-driven forms](#adding-cross-validation-to-template-driven-forms)

For a template-driven form, you must create a directive to wrap the validator function. You provide that directive as the validator using the [`NG_VALIDATORS` token](/api/forms/NG_VALIDATORS), as shown in the following example.

### unambiguous-role.directive.ts

```
@Directive({
  selector: '[appUnambiguousRole]',
  providers: [
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => UnambiguousRoleValidatorDirective),
      multi: true,
    },
  ],
})
export class UnambiguousRoleValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors | null {
    return unambiguousRoleValidator(control);
  }
}
```

You must add the new directive to the HTML template. Because the validator must be registered at the highest level in the form, the following template puts the directive on the `form` tag.

### actor-form-template.component.html

```
<form #actorForm="ngForm" appUnambiguousRole>
```

To provide better user experience, an appropriate error message appears when the form is invalid.

### actor-form-template.component.html

```
@if (actorForm.hasError('unambiguousRole') && (actorForm.touched || actorForm.dirty)) {
  <div class="cross-validation-error-message alert">Name cannot match role.</div>
}
```

This is the same in both template-driven and reactive forms.

## [Creating asynchronous validators](#creating-asynchronous-validators)

Asynchronous validators implement the [`AsyncValidatorFn`](/api/forms/AsyncValidatorFn) and [`AsyncValidator`](/api/forms/AsyncValidator) interfaces. These are very similar to their synchronous counterparts, with the following differences.

* The [`validate()`](/api/forms/signals/validate) functions must return a Promise or an observable,
* The observable returned must be finite, meaning that it must complete at some point. To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as `first`, `last`, `take`, or `takeUntil`.

Asynchronous validation happens after the synchronous validation, and is performed only if the synchronous validation is successful. This check lets forms avoid potentially expensive async validation processes (such as an HTTP request) if the more basic validation methods have already found invalid input.

After asynchronous validation begins, the form control enters a `pending` state. Inspect the control's `pending` property and use it to give visual feedback about the ongoing validation operation.

A common UI pattern is to show a spinner while the async validation is being performed. The following example shows how to achieve this in a template-driven form.

```
<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator />

@if (model.pending) {
  <app-spinner />
}
```

### [Implementing a custom async validator](#implementing-a-custom-async-validator)

In the following example, an async validator ensures that actors are cast for a role that is not already taken. New actors are constantly auditioning and old actors are retiring, so the list of available roles cannot be retrieved ahead of time. To validate the potential role entry, the validator must initiate an asynchronous operation to consult a central database of all currently cast actors.

The following code creates the validator class, `UniqueRoleValidator`, which implements the [`AsyncValidator`](/api/forms/AsyncValidator) interface.

### role.directive.ts

```
@Service()
export class UniqueRoleValidator implements AsyncValidator {
  private readonly actorsService = inject(ActorsService);

  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.actorsService.isRoleTaken(control.value).pipe(
      map((isTaken) => (isTaken ? {uniqueRole: true} : null)),
      catchError(() => of(null)),
    );
  }
}
```

The `actorsService` property is initialized with an instance of the `ActorsService` token, which defines the following interface.

```
interface ActorsService {
  isRoleTaken: (role: string) => Observable<boolean>;
}
```

In a real world application, the `ActorsService` would be responsible for making an HTTP request to the actor database to check if the role is available. From the validator's point of view, the actual implementation of the service is not important, so the example can just code against the `ActorsService` interface.

As the validation begins, the `UniqueRoleValidator` delegates to the `ActorsService` `isRoleTaken()` method with the current control value. At this point the control is marked as `pending` and remains in this state until the observable chain returned from the [`validate()`](/api/forms/signals/validate) method completes.

The `isRoleTaken()` method dispatches an HTTP request that checks if the role is available, and returns `Observable<boolean>` as the result. The [`validate()`](/api/forms/signals/validate) method pipes the response through the `map` operator and transforms it into a validation result.

The method then, like any validator, returns `null` if the form is valid, and [`ValidationErrors`](/api/forms/ValidationErrors) if it is not. This validator handles any potential errors with the `catchError` operator. In this case, the validator treats the `isRoleTaken()` error as a successful validation, because failure to make a validation request does not necessarily mean that the role is invalid. You could handle the error differently and return the [`ValidationError`](/api/forms/signals/ValidationError) object instead.

After some time passes, the observable chain completes and the asynchronous validation is done. The `pending` flag is set to `false`, and the form validity is updated.

### [Adding async validators to reactive forms](#adding-async-validators-to-reactive-forms)

To use an async validator in reactive forms, begin by injecting the validator into a property of the component class.

### actor-form-reactive.component.ts

```
roleValidator = inject(UniqueRoleValidator);
```

Then, pass the validator function directly to the [`FormControl`](/api/forms/FormControl) to apply it.

In the following example, the [`validate`](/api/forms/signals/validate) function of `UniqueRoleValidator` is applied to `roleControl` by passing it to the control's `asyncValidators` option and binding it to the instance of `UniqueRoleValidator` that was injected into `ActorFormReactiveComponent`. The value of `asyncValidators` can be either a single async validator function, or an array of functions. To learn more about [`FormControl`](/api/forms/FormControl) options, see the [AbstractControlOptions](api/forms/AbstractControlOptions) API reference.

### actor-form-reactive.component.ts

```
const roleControl = new FormControl('', {
  asyncValidators: [this.roleValidator.validate.bind(this.roleValidator)],
  updateOn: 'blur',
});
```

### [Adding async validators to template-driven forms](#adding-async-validators-to-template-driven-forms)

To use an async validator in template-driven forms, create a new directive and register the [`NG_ASYNC_VALIDATORS`](/api/forms/NG_ASYNC_VALIDATORS) provider on it.

In the example below, the directive injects the `UniqueRoleValidator` class that contains the actual validation logic and invokes it in the [`validate`](/api/forms/signals/validate) function, triggered by Angular when validation should happen.

### role.directive.ts

```
@Directive({
  selector: '[appUniqueRole]',
  providers: [
    {
      provide: NG_ASYNC_VALIDATORS,
      useExisting: forwardRef(() => UniqueRoleValidatorDirective),
      multi: true,
    },
  ],
})
export class UniqueRoleValidatorDirective implements AsyncValidator {
  private readonly validator = inject(UniqueRoleValidator);

  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.validator.validate(control);
  }
}
```

Then, as with synchronous validators, add the directive's selector to an input to activate it.

### actor-form-template.component.html (unique-unambiguous-role-input)

```
<input
  type="text"
  id="role"
  name="role"
  #role="ngModel"
  [(ngModel)]="actor.role"
  [ngModelOptions]="{updateOn: 'blur'}"
  appUniqueRole
/>
```

### [Optimizing performance of async validators](#optimizing-performance-of-async-validators)

By default, all validators run after every form value change. With synchronous validators, this does not normally have a noticeable impact on application performance. Async validators, however, commonly perform some kind of HTTP request to validate the control. Dispatching an HTTP request after every keystroke could put a strain on the backend API, and should be avoided if possible.

You can delay updating the form validity by changing the `updateOn` property from `change` (default) to [`submit`](/api/forms/signals/submit) or `blur`.

With template-driven forms, set the property in the template.

```
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}" />
```

With reactive forms, set the property in the [`FormControl`](/api/forms/FormControl) instance.

```
new FormControl('', {updateOn: 'blur'});
```

## [Managing validators dynamically in reactive forms](#managing-validators-dynamically-in-reactive-forms)

In complex reactive forms, you may need to add, remove, or modify validators based on user input or application state. Angular provides several methods on [`AbstractControl`](/api/forms/AbstractControl) to manage validators at runtime without recreating form controls.

### [Adding and removing validators](#adding-and-removing-validators)

The [`addValidators`](api/forms/AbstractControl#addValidators) and [`removeValidators`](api/forms/AbstractControl#removeValidators) methods allow you to modify a control's validators after initialization.

```
onCountryChange(country: string) {
    const postalCodeControl = this.profileForm.get('postalCode');

    if (country === 'US') {
      // Add validators for US postal codes
      postalCodeControl.addValidators([Validators.required, Validators.pattern(/^\d{5}$/)]);
    } else {
      // Remove validators when not US
      postalCodeControl.removeValidators([Validators.required]);
    }

    postalCodeControl.updateValueAndValidity();
}
```

### [Replacing all validators](#replacing-all-validators)

Use [`setValidators`](api/forms/AbstractControl#setValidators) to replace all existing synchronous validators on a control, or [`clearValidators`](api/forms/AbstractControl#clearValidators) to remove all validators.

```
toggleStrictNameValidation(isStrict: boolean) {
  const nameControl = this.profileForm.get('name');

  if (isStrict) {
    // Set strict validation rules
    nameControl.setValidators([
      Validators.required,
      Validators.minLength(3),
      Validators.pattern(/^[a-zA-Z]+$/),
    ]);
  } else {
    // Clear all validators
    nameControl.clearValidators();
  }

  nameControl.updateValueAndValidity();
}
```

The same pattern applies to async validators using [`addAsyncValidators`](api/forms/AbstractControl#addAsyncValidators), [`removeAsyncValidators`](api/forms/AbstractControl#removeAsyncValidators), [`setAsyncValidators`](api/forms/AbstractControl#setAsyncValidators), and [`clearAsyncValidators`](api/forms/AbstractControl#clearAsyncValidators).

### [Triggering validation updates](#triggering-validation-updates)

After modifying validators, call [`updateValueAndValidity`](api/forms/AbstractControl#updateValueAndValidity) to recalculate the control's validation status. This method accepts options to control update behavior.

```
// Update control and notify parent
control.updateValueAndValidity();

// Update control only, don't notify parent or emit events
control.updateValueAndValidity({onlySelf: true, emitEvent: false});
```

## [Interaction with native HTML form validation](#interaction-with-native-html-form-validation)

By default, Angular disables [native HTML form validation](https://developer.mozilla.org/docs/Web/Guide/HTML/Constraint_validation) by adding the `novalidate` attribute on the enclosing `<form>` and uses directives to match these attributes with validator functions in the framework. If you want to use native validation **in combination** with Angular-based validation, you can re-enable it with the `ngNativeValidate` directive. See the [API docs](api/forms/NgForm#native-dom-validation-ui) for details.

----
url: https://angular.dev/api/common/NgPlural
----

```
    
      class NgPlural {}
    
    
```

@param\_localization`NgLocalization`

@returns`NgPlural`

@paramvalue`string`

@paramswitchView`SwitchView`

@returns`void`

## [Description](#description)

Adds / removes DOM sub-trees based on a numeric value. Tailored for pluralization.

Displays DOM sub-trees that match the switch expression value, or failing that, DOM sub-trees that match the switch expression's pluralization category.

To use this directive you must provide a container element that sets the `[ngPlural]` attribute to a switch expression. Inner elements with a `[ngPluralCase]` will display based on their expression:

* if `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value matches the switch expression exactly,
* otherwise, the view will be treated as a "category match", and will only display if exact value matches aren't found and the value maps to its category for the defined locale.

See <http://cldr.unicode.org/index/cldr-spec/plural-rules>

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

```
<some-element [ngPlural]="value">
  <ng-template ngPluralCase="=0">there is nothing</ng-template>
  <ng-template ngPluralCase="=1">there is one</ng-template>
  <ng-template ngPluralCase="few">there are a few</ng-template>
</some-element>
```

Jump to details

----
url: https://angular.dev/api/router/ROUTES
----

```
    
      const ROUTES: InjectionToken<Route[][]>;
    
    
```

## [Description](#description)

The DI token for a router configuration.

[`ROUTES`](/api/router/ROUTES) is a low level API for router configuration via dependency injection.

We recommend that in almost all cases to use higher level APIs such as [`RouterModule.forRoot()`](/api/router/RouterModule#forRoot), [`provideRouter`](/api/router/provideRouter), or [`Router.resetConfig()`](/api/router/Router#resetConfig).

Jump to details

----
url: https://angular.dev/cli/generate/class
----

```
ng generate class [name] [options]
```

```
ng generate cl [name] [options]
```

Creates a new class in your project. Classes are the fundamental building blocks for object-oriented programming in TypeScript. They provide a blueprint for creating objects with properties and methods. This schematic helps you generate a new class with the basic structure and optional test files.

## [Arguments](#arguments)

`name`

The name for the new class. This will be used to create the class file (e.g., `my-class.ts`) and, if enabled, the corresponding test file `my-class.spec.ts`.

Value Type`string`

## [Options](#options)

`project`

The name of the project where the class should be added. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`skip-tests`

Skip the generation of a unit test file `spec.ts` for the new class.

Value Type`boolean`Default`false`

`type`

Adds a custom type to the filename, allowing you to create more descriptive class names. For example, if you set the type to `helper`, the filename will be `my-class.helper.ts`.

Value Type`string`

Jump to details

----
url: https://angular.dev/installation
----

Introduction

# Installation

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/installation.md "Edit this page")

Get started with Angular quickly with online starters or locally with your terminal.

## [Play Online](#play-online)

If you just want to play around with Angular in your browser without setting up a project, you can use our online sandbox:

### [Playground](/playground)

[The fastest way to play with an Angular app. No setup required.](/playground)

[Open on Playground](/playground)

## [Set up a new project locally](#set-up-a-new-project-locally)

If you're starting a new project, you'll most likely want to create a local project so that you can use tooling such as Git.

### [Prerequisites](#prerequisites)

* **Node.js** - [v22.22.3 or newer](/reference/versions)
* **Text editor** - We recommend [Visual Studio Code](https://code.visualstudio.com/)
* **Terminal** - Required for running [Angular CLI](/tools/cli) commands
* **Development Tool** - To improve your development workflow, we recommend the [Angular Language Service](/tools/language-service)

### [Instructions](#instructions)

The following guide will walk you through setting up a local Angular project.

#### [Install Angular CLI](#install-angular-cli)

Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.com/), you can open an [integrated terminal](https://code.visualstudio.com/docs/editor/integrated-terminal)) and run the following command:

### npm

```
npm install -g @angular/cli
```

### pnpm

```
pnpm install -g @angular/cli
```

### yarn

```
yarn global add @angular/cli
```

### bun

```
bun install -g @angular/cli
```

If you are having issues running this command in Windows or Unix, check out the [CLI docs](/tools/cli/setup-local#install-the-angular-cli) for more info.

#### [Create a new project](#create-a-new-project)

In your terminal, run the CLI command [`ng new`](cli/new) with the desired project name. In the following examples, we'll be using the example project name of `my-first-angular-app`.

```
ng new <project-name>
```

You will be presented with some configuration options for your project. Use the arrow and enter keys to navigate and select which options you desire.

If you don't have any preferences, just hit the enter key to take the default options and continue with the setup.

After you select the configuration options and the CLI runs through the setup, you should see the following message:

```
✔ Packages installed successfully.
    Successfully initialized git.
```

At this point, you're now ready to run your project locally!

#### [Running your new project locally](#running-your-new-project-locally)

In your terminal, switch to your new Angular project.

```
cd my-first-angular-app
```

All of your dependencies should be installed at this point (which you can verify by checking for the existence of a `node_modules` folder in your project), so you can start your project by running the command:

```
npm start
```

If everything is successful, you should see a similar confirmation message in your terminal:

```
Watch mode enabled. Watching for file changes...
NOTE: Raw file sizes do not reflect development server per-request transformations.
  ➜  Local:   http://localhost:4200/
  ➜  press h + enter to show help
```

And now you can visit the path in `Local` (e.g., `http://localhost:4200`) to see your application. Happy coding! 🎉

### [Using AI for Development](#using-ai-for-development)

To get started with building in your preferred AI powered IDE, [check out Angular prompt rules and best practices](/ai/develop-with-ai).

## [Next steps](#next-steps)

Now that you've created your Angular project, you can learn more about Angular in our [Essentials guide](/essentials) or choose a topic in our in-depth guides!

----
url: https://angular.dev/api/forms/signals/PATTERN
----

```
    
      const PATTERN: MetadataKey<Signal<RegExp[]>, RegExp | undefined, RegExp[]>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/readonly
----

```
function readonly<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  config?:
    | { when?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined }
    | undefined,
): void;
function readonly<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  logic?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined,
): void;
```

Adds logic to a field to conditionally make it readonly. A readonly field does not contribute to the validation, touched/dirty, or other state of its parent field.

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to make readonly.

@paramconfig`{ when?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined; } | undefined`

Optional configuration object.

* `when`: A reactive function that returns `true` when the field is readonly.

@returns`void`

Adds logic to a field to conditionally make it readonly.

@deprecated

Passing a function directly to `readonly` is deprecated. Use `{ when: ... }` instead.

@parampath`SchemaPath<TValue, 1, TPathKind>`

@paramlogic`NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/BaseRouteReuseStrategy
----

```
    
      abstract class BaseRouteReuseStrategy implements RouteReuseStrategy {}
    
    
```

Whether the given route should detach for later reuse. Always returns false for [`BaseRouteReuseStrategy`](/api/router/BaseRouteReuseStrategy).

@paramroute`ActivatedRouteSnapshot`

@returns`boolean`

A no-op; the route is never stored since this strategy never detaches routes for later re-use.

@paramroute`ActivatedRouteSnapshot`

@paramdetachedTree`DetachedRouteHandle`

@returns`void`

Returns `false`, meaning the route (and its subtree) is never reattached

@paramroute`ActivatedRouteSnapshot`

@returns`boolean`

Returns `null` because this strategy does not store routes for later re-use.

@paramroute`ActivatedRouteSnapshot`

@returns`DetachedRouteHandle | null`

Determines if a route should be reused. This strategy returns `true` when the future route config and current route config are identical.

@paramfuture`ActivatedRouteSnapshot`

@paramcurr`ActivatedRouteSnapshot`

@returns`boolean`

Determines if the injector for the given route should be destroyed.

This method is called by the router after navigations. If this method returns `true`, the router will destroy the injector for the given route.

@paramroute`Route`

@returns`boolean`

## [Description](#description)

This base route reuse strategy only reuses routes when the matched router configs are identical. This prevents components from being destroyed and recreated when just the route parameters, query parameters or fragment change (that is, the existing component is *reused*).

This strategy does not store any routes for later reuse.

Angular uses this strategy by default.

It can be used as a base class for custom route reuse strategies, i.e. you can create your own class that extends the [`BaseRouteReuseStrategy`](/api/router/BaseRouteReuseStrategy) one.

Jump to details

----
url: https://angular.dev/api/forms/signals/validateAsync
----

```
function validateAsync<
  TValue,
  TParams,
  TResult,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  opts: AsyncValidatorOptions<TValue, TParams, TResult, TPathKind>,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

A path indicating the field to bind the async validation logic to.

@paramopts`AsyncValidatorOptions<TValue, TParams, TResult, TPathKind>`

The async validation options.

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/Self
----

```
@Self();
```

## [Description](#description)

Parameter decorator to be used on constructor parameters, which tells the DI framework to start dependency resolution from the local injector.

Resolution works upward through the injector hierarchy, so the children of this class must configure their own providers or be prepared for a `null` result.

## [Usage Notes](#usage-notes)

In the following example, the dependency can be resolved by the local injector when instantiating the class itself, but not when instantiating a child.

```
class Dependency {}

        @Injectable()
        class NeedsDependency {
          constructor(@Self() public dependency: Dependency) {}
        }

        let inj = Injector.create({
          providers: [
            {provide: Dependency, deps: []},
            {provide: NeedsDependency, deps: [[new Self(), Dependency]]},
          ],
        });
        const nd = inj.get(NeedsDependency);

        expect(nd.dependency instanceof Dependency).toBe(true);

        const child = Injector.create({
          providers: [{provide: NeedsDependency, deps: [[new Self(), Dependency]]}],
          parent: inj,
        });
        expect(() => child.get(NeedsDependency)).toThrowError();
```

Jump to details

----
url: https://angular.dev/api/core/globals/getListeners
----

```
function getListeners(element: Element): Listener[];
```

@paramelement`Element`

Element for which the DOM listeners should be retrieved.

@returns`Listener[]`

Array of event listeners on the DOM element.

## [Usage Notes](#usage-notes)

Given the following DOM structure:

```
<app-root>
  <div (click)="doSomething()"></div>
</app-root>
```

Calling [`getListeners`](/api/core/globals/getListeners) on `<div>` will return an object that looks as follows:

```
{
  name: 'click',
  element: <div>,
  callback: () => doSomething(),
  useCapture: false
}
```

Jump to details

----
url: https://angular.dev/api/ssr/node/AngularNodeAppEngineOptions
----

```
    
      interface AngularNodeAppEngineOptions extends AngularAppEngineOptions {}
    
    
```

A set of allowed hostnames for the server application.

Extends the scope of trusted proxy headers (`Forwarded` or `X-Forwarded-*`).

Jump to details

----
url: https://angular.dev/api/common/getLocaleExtraDayPeriods
----

Angular recommends relying on the `Intl` API for i18n. To extract a day period use `Intl.DateTimeFormat` with the `dayPeriod` option instead.

## [API](#api)

```
function getLocaleExtraDayPeriods(
  locale: string,
  formStyle: FormStyle,
  width: TranslationWidth,
): string[];
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. To extract a day period use `Intl.DateTimeFormat` with the `dayPeriod` option instead.

@paramlocale`string`

A locale code for the locale format rules to use.

@paramformStyle`FormStyle`

The required grammatical form.

@paramwidth`TranslationWidth`

The required character width.

@returns`string[]`

The translated day-period strings.

## [Description](#description)

Retrieves locale-specific day periods, which indicate roughly how a day is broken up in different languages. For example, for `en-US`, periods are morning, noon, afternoon, evening, and midnight.

This functionality is only available when you have loaded the full locale data. See the ["I18n guide"](guide/i18n/format-data-locale).

Jump to details

----
url: https://angular.dev/api/core/resolveForwardRef
----

```
function resolveForwardRef<T>(type: T): T;
```

@paramtype`T`

@returns`T`

## [Description](#description)

Lazily retrieves the reference value from a forwardRef.

Acts as the identity function when given a non-forward-ref value.

## [Usage Notes](#usage-notes)

### [Example](#example)

```
const ref = forwardRef(() => 'refValue');
      expect(resolveForwardRef(ref as any)).toEqual('refValue');
      expect(resolveForwardRef('regularValue')).toEqual('regularValue');
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MAX_NUMBER
----

```
    
      const MAX_NUMBER: LimitKey<number>;
    
    
```

Jump to details

----
url: https://angular.dev/guide/components/outputs
----

Angular components can define custom events by assigning a property to the [`output`](/api/core/output) function:

```
@Component({
  /*...*/
})
export class ExpandablePanel {
  panelClosed = output<void>();
}
```

```
<expandable-panel (panelClosed)="savePanelState()" />
```

The [`output`](/api/core/output) function returns an [`OutputEmitterRef`](/api/core/OutputEmitterRef). You can emit an event by calling the `emit` method on the [`OutputEmitterRef`](/api/core/OutputEmitterRef):

```
this.panelClosed.emit();
```

Angular refers to properties initialized with the [`output`](/api/core/output) function as **outputs**. You can use outputs to raise custom events, similar to native browser events like `click`.

**Angular custom events do not bubble up the DOM**.

**Output names are case-sensitive.**

When extending a component class, **outputs are inherited by the child class.**

The [`output`](/api/core/output) function has special meaning to the Angular compiler. **You can exclusively call [`output`](/api/core/output) in component and directive property initializers.**

## [Emitting event data](#emitting-event-data)

You can pass event data when calling `emit`:

```
// You can emit primitive values.
this.valueChanged.emit(7);

// You can emit custom event objects
this.thumbDropped.emit({
  pointerX: 123,
  pointerY: 456,
});
```

When defining an event listener in a template, you can access the event data from the `$event` variable:

```
<custom-slider (valueChanged)="logValue($event)" />
```

Receive the event data in the parent component:

```
@Component({
 /*...*/
})
export class App {
  logValue(value: number) {
    ...
  }
}
```

## [Customizing output names](#customizing-output-names)

The [`output`](/api/core/output) function accepts a parameter that lets you specify a different name for the event in a template:

```
@Component(/* ... */)
export class CustomSlider {
  changed = output({alias: 'valueChanged'});
}
```

```
<custom-slider (valueChanged)="saveVolume()" />
```

This alias does not affect usage of the property in TypeScript code.

While you should generally avoid aliasing outputs for components, this feature can be useful for renaming properties while preserving an alias for the original name or for avoiding collisions with the name of native DOM events.

## [Subscribing to outputs programmatically](#subscribing-to-outputs-programmatically)

When creating a component dynamically, you can programmatically subscribe to output events from the component instance. The [`OutputRef`](/api/core/OutputRef) type includes a `subscribe` method:

```
const someComponentRef: ComponentRef<SomeComponent> = viewContainerRef.createComponent(/*...*/);

someComponentRef.instance.someEventProperty.subscribe((eventData) => {
  console.log(eventData);
});
```

Angular automatically cleans up event subscriptions when it destroys components with subscribers. Alternatively, you can manually unsubscribe from an event. The `subscribe` function returns an [`OutputRefSubscription`](/api/core/OutputRefSubscription) with an `unsubscribe` method:

```
const eventSubscription = someComponent.someEventProperty.subscribe((eventData) => {
  console.log(eventData);
});

// ...

eventSubscription.unsubscribe();
```

## [Choosing event names](#choosing-event-names)

Avoid choosing output names that collide with events on DOM elements like HTMLElement. Name collisions introduce confusion about whether the bound property belongs to the component or the DOM element.

Avoid adding prefixes for component outputs like you would with component selectors. Since a given element can only host one component, any custom properties can be assumed to belong to the component.

Always use [camelCase](https://en.wikipedia.org/wiki/Camel_case) output names. Avoid prefixing output names with "on".

## [Using outputs with RxJS](#using-outputs-with-rxjs)

See [RxJS interop with component and directive outputs](ecosystem/rxjs-interop/output-interop) for details on interoperability between outputs and RxJS.

## [Declaring outputs with the `@Output` decorator](#declaring-outputs-with-the-output-decorator)

**TIP:** While the Angular team recommends using the [`output`](/api/core/output) function for new projects, the original decorator-based [`@Output`](/api/core/Output) API remains fully supported.

You can alternatively define custom events by assigning a property to a new [`EventEmitter`](/api/core/EventEmitter) and adding the [`@Output`](/api/core/Output) decorator:

```
@Component(/* ... */)
export class ExpandablePanel {
  @Output() panelClosed = new EventEmitter<void>();
}
```

You can emit an event by calling the `emit` method on the [`EventEmitter`](/api/core/EventEmitter).

### [Aliases with the `@Output` decorator](#aliases-with-the-output-decorator)

The [`@Output`](/api/core/Output) decorator accepts a parameter that lets you specify a different name for the event in a template:

```
@Component(/* ... */)
export class CustomSlider {
  @Output('valueChanged') changed = new EventEmitter<number>();
}
```

```
<custom-slider (valueChanged)="saveVolume()" />
```

This alias does not affect usage of the property in TypeScript code.

## [Specify outputs in the `@Component` decorator](#specify-outputs-in-the-component-decorator)

In addition to the [`@Output`](/api/core/Output) decorator, you can also specify a component's outputs with the `outputs` property in the [`@Component`](/api/core/Component) decorator. This can be useful when a component inherits a property from a base class:

```
// `CustomSlider` inherits the `valueChanged` property from `BaseSlider`.
@Component({
  /*...*/
  outputs: ['valueChanged'],
})
export class CustomSlider extends BaseSlider {}
```

You can additionally specify an output alias in the `outputs` list by putting the alias after a colon in the string:

```
// `CustomSlider` inherits the `valueChanged` property from `BaseSlider`.
@Component({
  /*...*/
  outputs: ['valueChanged: volumeChanged'],
})
export class CustomSlider extends BaseSlider {}
```

----
url: https://angular.dev/api/core/numberAttribute
----

```
function numberAttribute(value: unknown, fallbackValue?: number): number;
```

@paramvalue`unknown`

Value to be transformed.

@paramfallbackValue`number`

Value to use if the provided value can't be parsed as a number.

@returns`number`

## [Usage Notes](#usage-notes)

```
status = input({ transform: numberAttribute });
```

Jump to details

----
url: https://angular.dev/api/core/twoWayBinding
----

```
function twoWayBinding(
  publicName: string,
  value: WritableSignal<unknown>,
): Binding;
```

@parampublicName`string`

Public name of the two-way compatible input.

@paramvalue`WritableSignal<unknown>`

Writable signal from which to get the current value and to which to write new values.

### [Usage example](#usage-example)

In this example we create an instance of the `MyCheckbox` component and bind to its `value` input using a two-way binding.

```
const checkboxValue = signal('');

createComponent(MyCheckbox, {
bindings: [
twoWayBinding('value', checkboxValue),
],
});
```

@returns`Binding`

Jump to details

----
url: https://angular.dev/api/router/PRIMARY_OUTLET
----

```
    
      const PRIMARY_OUTLET: string;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/provideRouter
----

```
function provideRouter(
  routes: Routes,
  ...features: RouterFeatures[]
): EnvironmentProviders;
```

@paramroutes`Routes`

A set of [`Route`](/api/router/Route)s to use for the application routing table.

@paramfeatures`RouterFeatures[]`

Optional features to configure additional router behaviors.

@returns`EnvironmentProviders`

A set of providers to setup a Router.

## [Usage Notes](#usage-notes)

Basic example of how you can add a Router to your application:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent, {
  providers: [provideRouter(appRoutes)]
});
```

You can also enable optional features in the Router by adding functions from the [`RouterFeatures`](/api/router/RouterFeatures) type:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes,
        withDebugTracing(),
        withRouterConfig({paramsInheritanceStrategy: 'always'}))
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/guide/forms/signals/form-logic
----

Signal Forms allow you to add logic to your form using schemas. Validation logic is covered in the [Validation guide](guide/forms/signals/validation), and this guide discusses other rules available in schemas. You can disable fields conditionally, hide them based on other values, make them readonly, debounce user input, and attach metadata for custom controls.

This guide shows you how to use rules like [`disabled()`](/api/forms/signals/disabled), [`hidden()`](/api/forms/signals/hidden), [`readonly()`](/api/forms/signals/readonly), [`debounce()`](/api/forms/signals/debounce), and [`metadata()`](/api/forms/signals/metadata) to control field behavior.

## [When to add form logic](#when-to-add-form-logic)

Use rules when field behavior depends on other field values or needs to update reactively. For example:

* A coupon code field that's disabled when the order total is too low
* An address field that's hidden unless shipping is required
* A search field that debounces to reduce API calls

## [How rules work](#how-rules-work)

Rules bind reactive logic to specific fields in your form. Most conditional rules accept an options object with a `when` function. The `when` function automatically recomputes whenever the signals it references change, just like a [`computed`](/api/core/computed).

```
const orderForm = form(this.orderModel, (schemaPath) => {
  disabled(schemaPath.couponCode, {when: ({valueOf}) => valueOf(schemaPath.total) < 50});
  //~~~~~~ ~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //rule     path                   reactive logic function
});
```

Reactive logic functions receive a [`FieldContext`](/api/forms/signals/FieldContext) object that provides access to field values and state through helper functions like `valueOf()` and `stateOf()`. It is often destructured to access these helpers directly.

**NOTE:** The schema callback parameter (`schemaPath` in these examples) is a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) object that provides paths to all fields in your form. You can name this parameter anything you like.

For complete details on [`FieldContext`](/api/forms/signals/FieldContext) properties and methods, see the [Validation guide](guide/forms/signals/validation).

## [Prevent field updates with `disabled()`](#prevent-field-updates-with-disabled)

The [`disabled()`](/api/forms/signals/disabled) rule configures a field's disabled state.

It works with the `[formField]` directive to automatically bind the `disabled` attribute based on the field's state, so you don't need to manually add `[disabled]="yourForm.fieldName().disabled()"` to your template.

**NOTE:** Disabled fields skip validation - they don't participate in form validation checks. The field's value is preserved but not validated. For details on validation behavior, see the [Validation guide](guide/forms/signals/validation).

### [Always disabled](#always-disabled)

To disable a field permanently, call [`disabled()`](/api/forms/signals/disabled) with just the field path:

```
import {Component, signal} from '@angular/core';
import {form, FormField, disabled} from '@angular/forms/signals';

@Component({
  selector: 'app-settings',
  imports: [FormField],
  template: `
    <label>
      System ID (cannot be changed)
      <input [formField]="settingsForm.systemId" />
    </label>
  `,
})
export class Settings {
  settingsModel = signal({
    systemId: 'SYS-12345',
    userName: '',
  });

  settingsForm = form(this.settingsModel, (schemaPath) => {
    disabled(schemaPath.systemId);
  });
}
```

### [Conditional disabling](#conditional-disabling)

To disable a field based on conditions, provide a `when` function that returns `true` (disabled) or `false` (enabled):

```
import {Component, signal} from '@angular/core';
import {form, FormField, disabled} from '@angular/forms/signals';

@Component({
  selector: 'app-order',
  imports: [FormField],
  template: `
    <label>
      Order Total
      <input type="number" [formField]="orderForm.total" />
    </label>

    <label>
      Coupon Code
      <input [formField]="orderForm.couponCode" />
    </label>
  `,
})
export class Order {
  orderModel = signal({
    total: 25,
    couponCode: '',
  });

  orderForm = form(this.orderModel, (schemaPath) => {
    disabled(schemaPath.couponCode, {when: ({valueOf}) => valueOf(schemaPath.total) < 50});
  });
}
```

In this example, when the order total is less than $50, the coupon code field is disabled.

### [Disabled reasons](#disabled-reasons)

When you disable a field, provide user-facing explanations by returning a string instead of `true`:

```
import {Component, signal} from '@angular/core';
import {form, FormField, disabled} from '@angular/forms/signals';

@Component({
  selector: 'app-order',
  imports: [FormField],
  template: `
    <label>
      Order Total
      <input type="number" [formField]="orderForm.total" />
    </label>

    <label>
      Coupon Code
      <input [formField]="orderForm.couponCode" />
    </label>

    @if (orderForm.couponCode().disabled()) {
      <div class="info">
        @for (reason of orderForm.couponCode().disabledReasons(); track reason) {
          <p>{{ reason.message }}</p>
        }
      </div>
    }
  `,
})
export class Order {
  orderModel = signal({
    total: 25,
    couponCode: '',
  });

  orderForm = form(this.orderModel, (schemaPath) => {
    disabled(schemaPath.couponCode, {
      when: ({valueOf}) =>
        valueOf(schemaPath.total) < 50 ? 'Order must be $50 or more to use a coupon' : false,
    });
  });
}
```

The `when` function returns:

* A **string** to disable the field with a reason
* `false` to enable the field (not just any falsy value - use `false` explicitly)

Access the reasons through the `disabledReasons()` signal on the field state. Each reason has a `message` property containing the string you returned.

#### [Multiple disabled reasons](#multiple-disabled-reasons)

You can also call [`disabled()`](/api/forms/signals/disabled) multiple times on the same field, and all of the returned reasons accumulate:

```
orderForm = form(this.orderModel, (schemaPath) => {
  disabled(schemaPath.promoCode, {
    when: ({valueOf}) =>
      !valueOf(schemaPath.hasAccount) ? 'You must have an account to use promo codes' : false,
  });
  disabled(schemaPath.promoCode, {
    when: ({valueOf}) => (valueOf(schemaPath.total) < 25 ? 'Order must be at least $25' : false),
  });
});
```

If both conditions are true, the field shows both disabled reasons. This pattern is useful for complex availability rules that you want to keep separate.

## [Configuring `hidden()` state on fields](#configuring-hidden-state-on-fields)

The [`hidden()`](/api/forms/signals/hidden) rule configures a field's hidden state. However, this only sets a programmatic state. **You control whether the field appears in the UI**.

**IMPORTANT:** Unlike `disabled` and `readonly`, there is no native DOM property for `hidden` state. The `[formField]` directive does not apply a `hidden` attribute to elements. You must use `@if` or CSS in your template to conditionally render fields based on the [`hidden()`](/api/forms/signals/hidden) state.

**NOTE:** Like disabled fields, hidden fields also skip validation. See the [Validation guide](guide/forms/signals/validation) for details.

### [Basic field hiding](#basic-field-hiding)

Use [`hidden()`](/api/forms/signals/hidden) with a `when` function that returns `true` (hidden) or `false` (visible):

```
import {Component, signal} from '@angular/core';
import {form, FormField, hidden} from '@angular/forms/signals';

@Component({
  selector: 'app-profile',
  imports: [FormField],
  template: `
    <label>
      <input type="checkbox" [formField]="profileForm.isPublic" />
      Make profile public
    </label>

    @if (!profileForm.publicUrl().hidden()) {
      <label>
        Public URL
        <input [formField]="profileForm.publicUrl" />
      </label>
    }
  `,
})
export class Profile {
  profileModel = signal({
    isPublic: false,
    publicUrl: '',
  });

  profileForm = form(this.profileModel, (schemaPath) => {
    hidden(schemaPath.publicUrl, {when: ({valueOf}) => !valueOf(schemaPath.isPublic)});
  });
}
```

### [Always hidden](#always-hidden)

To make a field permanently hidden, call [`hidden()`](/api/forms/signals/hidden) with just the field path:

```
import {Component, signal} from '@angular/core';
import {form, FormField, hidden} from '@angular/forms/signals';

@Component({
  selector: 'app-profile',
  imports: [FormField],
  template: `
    <label>
      <input type="checkbox" [formField]="profileForm.isPublic" />
      Make profile public
    </label>

    <!-- The publicUrl is permanently hidden, so this block never renders -->
    @if (!profileForm.publicUrl().hidden()) {
      <label>
        Public URL
        <input [formField]="profileForm.publicUrl" />
      </label>
    }
  `,
})
export class Profile {
  profileModel = signal({
    isPublic: false,
    publicUrl: '',
  });

  profileForm = form(this.profileModel, (schemaPath) => {
    // This field is now permanently hidden and excluded from active validation
    hidden(schemaPath.publicUrl);
  });
}
```

## [Display uneditable fields with `readonly()`](#display-uneditable-fields-with-readonly)

The [`readonly()`](/api/forms/signals/readonly) rule prevents users from updating a field. The `[FormField]` directive automatically binds this state to the HTML `readonly` attribute, which prevents editing while still allowing users to focus and select text.

**NOTE:** Readonly fields skip [validation](guide/forms/signals/validation).

### [Always readonly](#always-readonly)

To make a field permanently readonly, call [`readonly()`](/api/forms/signals/readonly) with just the field path:

```
import {Component, signal} from '@angular/core';
import {form, FormField, readonly} from '@angular/forms/signals';

@Component({
  selector: 'app-account',
  imports: [FormField],
  template: `
    <label>
      Username (cannot be changed)
      <input [formField]="accountForm.username" />
    </label>

    <label>
      Email
      <input [formField]="accountForm.email" />
    </label>
  `,
})
export class Account {
  accountModel = signal({
    username: 'johndoe',
    email: 'john@example.com',
  });

  accountForm = form(this.accountModel, (schemaPath) => {
    readonly(schemaPath.username);
  });
}
```

The `[FormField]` directive automatically binds the `readonly` attribute based on the field's state.

### [Conditional readonly](#conditional-readonly)

To make a field readonly based on conditions, provide a `when` function:

```
import {Component, signal} from '@angular/core';
import {form, FormField, readonly} from '@angular/forms/signals';

@Component({
  selector: 'app-document',
  imports: [FormField],
  template: `
    <label>
      <input type="checkbox" [formField]="documentForm.isLocked" />
      Lock document
    </label>

    <label>
      Document Title
      <input [formField]="documentForm.title" />
    </label>
  `,
})
export class Document {
  documentModel = signal({
    isLocked: false,
    title: 'Untitled',
  });

  documentForm = form(this.documentModel, (schemaPath) => {
    readonly(schemaPath.title, {when: ({valueOf}) => valueOf(schemaPath.isLocked)});
  });
}
```

When `isLocked` is true, the title field becomes readonly.

## [Choose between hidden, disabled, and readonly](#choose-between-hidden-disabled-and-readonly)

These three configuration functions control field availability in different ways:

Choose [`hidden()`](/api/forms/signals/hidden) when the field:

* Should not appear in the UI at all
* Is irrelevant to the current form state
* Example: Shipping address fields when "same as billing" is checked

Choose [`disabled()`](/api/forms/signals/disabled) when the field:

* Should be visible but not editable
* Needs to show why it's unavailable (using disabled reasons)
* Should be excluded from HTML form submission
* Example: Submit button disabled until form is valid, approval fields disabled for non-admin users

Choose [`readonly()`](/api/forms/signals/readonly) when the field:

* Should be visible but not editable
* Contains data users need to see, select, or copy
* Should be included in HTML form submission
* Example: Order confirmation number, system-generated reference codes

All three skip validation and prevent user editing while active. The key differences:

| Feature                          | [`hidden()`](/api/forms/signals/hidden) | [`disabled()`](/api/forms/signals/disabled) | [`readonly()`](/api/forms/signals/readonly) |
| -------------------------------- | --------------------------------------- | ------------------------------------------- | ------------------------------------------- |
| Visible in UI                    | No                                      | Yes                                         | Yes                                         |
| Users can focus/select           | No                                      | No                                          | Yes                                         |
| Included in HTML form submission | No                                      | No                                          | Yes                                         |

## [Delay input operations with `debounce()`](#delay-input-operations-with-debounce)

The [`debounce()`](/api/forms/signals/debounce) rule delays updating the form model. This is useful for performance optimization and reducing unnecessary operations during rapid input.

### [What debouncing does](#what-debouncing-does)

Without debouncing, every keystroke immediately updates the form model. This can trigger:

* Expensive computed signals that recalculate on every change
* Validation checks after each character
* API calls or other side effects tied to the model value

Debouncing delays these updates and reduces unnecessary work.

### [Basic debouncing](#basic-debouncing)

You can debounce a field by specifying a delay in milliseconds:

```
import {Component, signal} from '@angular/core';
import {form, FormField, debounce} from '@angular/forms/signals';

@Component({
  selector: 'app-search',
  imports: [FormField],
  template: `
    <label>
      Search
      <input [formField]="searchForm.query" />
    </label>

    <p>Searching for: {{ searchForm.query().value() }}</p>
  `,
})
export class Search {
  searchModel = signal({
    query: '',
  });

  searchForm = form(this.searchModel, (schemaPath) => {
    debounce(schemaPath.query, 300);
  });
}
```

With a 300ms debounce:

* User types in the input field
* Form model updates only after 300ms of typing inactivity
* If user keeps typing, the timer resets with each keystroke
* Once user pauses for 300ms, the model updates with the final value

### [Timing guarantees](#timing-guarantees)

The [`debounce()`](/api/forms/signals/debounce) function ensures users don't lose data through these mechanisms:

* **When marked as touched:** The value syncs immediately, aborting any pending debounce delay. This happens when the field loses focus (blur) or when explicitly marked as touched.
* **On form submission:** All fields are marked as touched before validation, which ensures all debounced values sync immediately.

This means users can type quickly, tab away, or submit the form without waiting for debounce delays to expire.

### [Custom debounce logic](#custom-debounce-logic)

For advanced control, provide a debouncer function that controls when to synchronize the value. This function is called every time the control value is updated and can return either `undefined` to synchronize immediately, or a Promise that prevents synchronization until it resolves:

```
import {Component, signal} from '@angular/core';
import {form, FormField, debounce} from '@angular/forms/signals';

@Component({
  selector: 'app-search',
  imports: [FormField],
  template: `
    <label>
      Search
      <input [formField]="searchForm.query" />
    </label>
  `,
})
export class Search {
  searchModel = signal({
    query: '',
  });

  searchForm = form(this.searchModel, (schemaPath) => {
    debounce(schemaPath.query, () => {
      // Return a promise that resolves after 500ms
      return new Promise<void>((resolve) => {
        setTimeout(() => resolve(), 500);
      });
    });
  });
}
```

The debouncer function can return:

* `undefined` to synchronize the value immediately
* A `Promise<void>` that prevents synchronization until it resolves

Use cases for custom debounce logic:

* Implementing custom timing logic beyond simple delays
* Coordinating synchronization with external events
* Conditional debouncing based on application state

### [When to use debouncing](#when-to-use-debouncing)

Debouncing is most useful when:

* You have expensive computed signals that depend on the field value
* The field triggers API calls or other side effects
* You want to reduce validation overhead during rapid typing
* Performance profiling shows model updates are causing slowdowns

Don't use debouncing if:

* The field needs immediate updates for good UX (such as calculator inputs)
* The performance benefit is negligible
* Users expect real-time feedback

## [Associate data with a field using `metadata()`](#associate-data-with-a-field-using-metadata)

Metadata attaches reactive data to a field. Validation rules use this system internally, and you can publish your own keys for application-specific information like help text, configuration, or computed display values.

Signal Forms provides pre-defined metadata keys that built-in validators populate automatically:

| Key                                           | Populated by                                       | Read via              |
| --------------------------------------------- | -------------------------------------------------- | --------------------- |
| [`REQUIRED`](/api/forms/signals/REQUIRED)     | `required()`                                       | `field().required()`  |
| [`MIN`](/api/forms/signals/MIN)               | `min()`, [`minDate()`](/api/forms/signals/minDate) | `field().min()`       |
| [`MAX`](/api/forms/signals/MAX)               | `max()`, [`maxDate()`](/api/forms/signals/maxDate) | `field().max()`       |
| [`MIN_LENGTH`](/api/forms/signals/MIN_LENGTH) | [`minLength()`](/api/forms/signals/minLength)      | `field().minLength()` |
| [`MAX_LENGTH`](/api/forms/signals/MAX_LENGTH) | [`maxLength()`](/api/forms/signals/maxLength)      | `field().maxLength()` |
| [`PATTERN`](/api/forms/signals/PATTERN)       | `pattern()`                                        | `field().pattern()`   |

The `[formField]` directive automatically binds five of these ([`REQUIRED`](/api/forms/signals/REQUIRED), [`MIN`](/api/forms/signals/MIN), [`MAX`](/api/forms/signals/MAX), [`MIN_LENGTH`](/api/forms/signals/MIN_LENGTH), and [`MAX_LENGTH`](/api/forms/signals/MAX_LENGTH)) to the corresponding HTML attribute on a native form control. [`PATTERN`](/api/forms/signals/PATTERN) is the exception, because Signal Forms supports multiple patterns per field but the HTML `pattern` attribute accepts only a single regular expression.

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, min, max} from '@angular/forms/signals';

@Component({
  selector: 'app-age',
  imports: [FormField],
  template: `
    <label>
      Age (between {{ ageForm.age().min?.() }} and {{ ageForm.age().max?.() }})
      <input type="number" [formField]="ageForm.age" />
    </label>

    @if (ageForm.age().required()) {
      <span class="required-indicator">*</span>
    }
  `,
})
export class Age {
  ageModel = signal({age: 0});

  ageForm = form(this.ageModel, (schemaPath) => {
    required(schemaPath.age);
    min(schemaPath.age, 18);
    max(schemaPath.age, 120);
  });
}
```

### [Reactive metadata](#reactive-metadata)

Validation rules can derive their constraints from other fields, making the published metadata reactive:

```
import {Component, signal} from '@angular/core';
import {form, FormField, max} from '@angular/forms/signals';

@Component({
  selector: 'app-inventory',
  imports: [FormField],
  template: `
    <label>
      Item
      <select [formField]="inventoryForm.item">
        <option value="widget">Widget</option>
        <option value="gadget">Gadget</option>
      </select>
    </label>

    <label>
      Quantity (max: {{ inventoryForm.quantity().max?.() }})
      <input type="number" [formField]="inventoryForm.quantity" />
    </label>
  `,
})
export class Inventory {
  inventoryModel = signal({
    item: 'widget',
    quantity: 0,
  });

  inventoryForm = form(this.inventoryModel, (schemaPath) => {
    max(schemaPath.quantity, ({valueOf}) => {
      const item = valueOf(schemaPath.item);
      return item === 'widget' ? 100 : 50;
    });
  });
}
```

The `max()` validation rule sets the [`MAX`](/api/forms/signals/MAX) metadata reactively based on the selected item, so any template or control reading `field().max()` updates whenever the item changes.

For deeper coverage, including how to define custom keys, combine contributions with reducers, and use managed metadata for lifecycle-aware objects, see the [Field metadata guide](guide/forms/signals/field-metadata).

## [Combining rules](#combining-rules)

You can apply multiple rules to the same field, and you can use conditional logic to apply entire groups of rules based on form state.

### [Multiple rules on one field](#multiple-rules-on-one-field)

Apply multiple rules to configure all aspects of a field's behavior:

```
import {Component, signal} from '@angular/core';
import {form, FormField, disabled, hidden, debounce, metadata} from '@angular/forms/signals';
import {PLACEHOLDER} from './metadata-keys';

@Component({
  selector: 'app-promo',
  imports: [FormField],
  template: `
    @if (!promoForm.promoCode().hidden()) {
      <label>
        Promo Code
        <input [formField]="promoForm.promoCode" />
      </label>
    }
  `,
})
export class Promo {
  promoModel = signal({
    hasAccount: false,
    subscriptionType: 'free' as 'free' | 'premium',
    promoCode: '',
  });

  promoForm = form(this.promoModel, (schemaPath) => {
    disabled(schemaPath.promoCode, {
      when: ({valueOf}) => (!valueOf(schemaPath.hasAccount) ? 'You must have an account' : false),
    });
    hidden(schemaPath.promoCode, {
      when: ({valueOf}) => valueOf(schemaPath.subscriptionType) === 'free',
    });
    debounce(schemaPath.promoCode, 300);
    metadata(schemaPath.promoCode, PLACEHOLDER, () => 'Enter promo code');
  });
}
```

These rules work together:

* Hidden takes precedence - if the field is hidden, disabled state doesn't matter
* Disabled prevents editing regardless of readonly state
* Debouncing affects model updates regardless of other state
* Metadata is independent and always available

### [Conditional logic with applyWhen](#conditional-logic-with-applywhen)

Use [`applyWhen()`](/api/forms/signals/applyWhen) to conditionally apply entire groups of rules:

```
import {Component, signal} from '@angular/core';
import {form, FormField, applyWhen, required, pattern} from '@angular/forms/signals';

@Component({
  selector: 'app-address',
  imports: [FormField],
  template: `
    <label>
      Country
      <select [formField]="addressForm.country">
        <option value="US">United States</option>
        <option value="CA">Canada</option>
      </select>
    </label>

    <label>
      Zip/Postal Code
      <input [formField]="addressForm.zipCode" />
    </label>
  `,
})
export class Address {
  addressModel = signal({
    country: 'US',
    zipCode: '',
  });

  addressForm = form(this.addressModel, (schemaPath) => {
    applyWhen(
      schemaPath,
      ({valueOf}) => valueOf(schemaPath.country) === 'US',
      (schemaPath) => {
        // Only applied when country is US
        required(schemaPath.zipCode);
        pattern(schemaPath.zipCode, /^\d{5}(-\d{4})?$/);
      },
    );
  });
}
```

The [`applyWhen()`](/api/forms/signals/applyWhen) function receives:

1. A path to apply logic to (often the root form path)
2. A reactive logic function that returns `true` (apply) or `false` (don't apply)
3. A schema function that defines the conditional rules

The conditional rules only run when the condition is true. This is useful for complex forms where validation rules or behavior changes based on user choices.

### [Reusable schema functions](#reusable-schema-functions)

Extract common rule configurations into reusable functions:

```
import {SchemaPath, debounce, metadata, maxLength} from '@angular/forms/signals';
import {PLACEHOLDER} from './metadata-keys';

function emailFieldConfig(path: SchemaPath<string>) {
  debounce(path, 300);
  metadata(path, PLACEHOLDER, () => 'user@example.com');
  maxLength(path, 255);
}

// Use in multiple forms
const contactForm = form(contactModel, (schemaPath) => {
  emailFieldConfig(schemaPath.email);
  emailFieldConfig(schemaPath.alternateEmail);
});

const registrationForm = form(registrationModel, (schemaPath) => {
  emailFieldConfig(schemaPath.email);
});
```

This pattern is useful when you have standard field configurations that you use across multiple forms in your application.

## [Next steps](#next-steps)

To learn more about Signal Forms, check out these related guides:

* [Field State Management](guide/forms/signals/field-state-management) - Learn how to use the state signals created by these functions in your templates and component logic
* [Validation](guide/forms/signals/validation) - Learn about validation rules and error handling
* [Custom Controls](guide/forms/signals/custom-controls) - Learn how custom controls can read metadata and state to configure themselves automatically

----
url: https://angular.dev/guide/i18n/import-global-variants
----

The [Angular CLI](cli "CLI Overview and Command Reference | Angular") automatically includes locale data if you run the [`ng build`](cli/build "ng build | CLI | Angular") command with the `--localize` option.

```
ng build --localize
```

**HELPFUL:** The initial installation of Angular already contains locale data for English in the United States (`en-US`). The [Angular CLI](cli "CLI Overview and Command Reference | Angular") automatically includes the locale data and sets the [`LOCALE_ID`](/api/core/LOCALE_ID) value when you use the `--localize` option with [`ng build`](cli/build "ng build | CLI | Angular") command.

The `@angular/common` package on npm contains the locale data files. Global variants of the locale data are available in `@angular/common/locales/global`.

## [`import` example for French](/guide/i18n/import-global-variants#import-example-for-french)

For example, you could import the global variants for French (`fr`) in `main.ts` where you bootstrap the application.

### src/main.ts (import locale)

```
import '@angular/common/locales/global/fr';
```

**HELPFUL:** In an `NgModules` application, you would import it in your `app.module`.

----
url: https://angular.dev/api/forms/signals/compat/NG_STATUS_CLASSES
----

```
    
      const NG_STATUS_CLASSES: SignalFormsConfig;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/ExtraOptions
----

```
    
      interface ExtraOptions extends InMemoryScrollingOptions, RouterConfigOptions {}
    
    
```

When true, log all internal navigation events to the console. Use for debugging.

When true, enable the location strategy that uses the URL fragment instead of the history API.

One of `enabled`, `enabledBlocking`, `enabledNonBlocking` or `disabled`. When set to `enabled` or `enabledBlocking`, the initial navigation starts before the root component is created. The bootstrap is blocked until the initial navigation is complete. This value should be set in case you use [server-side rendering](guide/ssr), but do not enable [hydration](guide/hydration) for your application. When set to `enabledNonBlocking`, the initial navigation starts after the root component has been created. The bootstrap is not blocked on the completion of the initial navigation. When set to `disabled`, the initial navigation is not performed. The location listener is set up before the root component gets created. Use if there is a reason to have more control over when the router starts its initial navigation due to some complex initialization logic.

When true, enables binding information from the [`Router`](/api/router/Router) state directly to the inputs of the component in [`Route`](/api/router/Route) configurations. Can also accept an [`ComponentInputBindingOptions`](/api/router/ComponentInputBindingOptions) object to set whether to exclude queryParams from binding.

@experimental

When true, enables view transitions in the Router by running the route activation and deactivation inside of `document.startViewTransition`.

A custom error handler for failed navigations. If the handler returns a value, the navigation Promise is resolved with this value. If the handler throws an exception, the navigation Promise is rejected with the exception.

Configures a preloading strategy. One of [`PreloadAllModules`](/api/router/PreloadAllModules) or [`NoPreloading`](/api/router/NoPreloading) (the default).

Configures the scroll offset the router will use when scrolling to an element.

When given a tuple with x and y position value, the router uses that offset each time it scrolls. When given a function, the router invokes the function every time it restores scroll position.

```
class App {
  movieData = signal<MovieData | null>(null);
  private router = inject(Router);
  private viewportScroller = inject(ViewportScroller);
  private changeDetectorRef = inject(ChangeDetectorRef);

  constructor() {
   this.router.events.pipe(filter((event: Event): event is Scroll => event instanceof Scroll)
    ).subscribe(e => {
      fetch('http://example.com/movies.json').then(response => {
        this.movieData.set(response.json());

        if (e.position) {
          this.viewportScroller.scrollToPosition(e.position);
        }
      });
    });
  }
}
```

----
url: https://angular.dev/api/router/RouteReuseStrategy
----

```
    
      abstract class RouteReuseStrategy {}
    
    
```

Determines if this route (and its subtree) should be detached to be reused later

@paramroute`ActivatedRouteSnapshot`

@returns`boolean`

Stores the detached route.

Storing a `null` value should erase the previously stored value.

@paramroute`ActivatedRouteSnapshot`

@paramhandle`DetachedRouteHandle | null`

@returns`void`

Determines if this route (and its subtree) should be reattached

@paramroute`ActivatedRouteSnapshot`

@returns`boolean`

Retrieves the previously stored route

@paramroute`ActivatedRouteSnapshot`

@returns`DetachedRouteHandle | null`

Determines if a route should be reused.

Note: Recreating a component by returning `false` does not automatically rerun guards or resolvers; rerun behavior for an unchanged route configuration is controlled by [`RunGuardsAndResolvers`](/api/router/RunGuardsAndResolvers).

@paramfuture`ActivatedRouteSnapshot`

@paramcurr`ActivatedRouteSnapshot`

@returns`boolean`

Returns a list of all currently stored [`DetachedRouteHandle`](/api/router/DetachedRouteHandle)s.

This method is called by the router after navigations to identify which injectors are still needed by detached routes.

@returns`DetachedRouteHandle[]`

Determines if the injector for the given route should be destroyed.

If this method returns `true`, the router will destroy the injector for the given route.

@paramroute`Route`

@returns`boolean`

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8113
----

This diagnostic detects cases where the `imports` array of a [`@Component`](/api/core/Component) contains symbols that aren't used within the template.

```
@Component({
  imports: [UsedDirective, UnusedPipe],
})
class AwesomeCheckbox {}
```

## [What's wrong with that?](#whats-wrong-with-that)

The unused imports add unnecessary noise to your code and can increase your compilation time.

## [What should I do instead?](#what-should-i-do-instead)

Delete the unused import.

```
@Component({
  imports: [UsedDirective],
})
class AwesomeCheckbox {}
```

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "unusedStandaloneImports": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/guide/templates/two-way-binding
----

**Two way binding** is a shorthand to simultaneously bind a value into an element, while also giving that element the ability to propagate changes back through this binding.

## [Syntax](#syntax)

The syntax for two-way binding is a combination of square brackets and parentheses, `[()]`. It combines the syntax from property binding, `[]`, and the syntax from event binding, `()`. The Angular community informally refers to this syntax as "banana-in-a-box".

## [Two-way binding with form controls](#two-way-binding-with-form-controls)

Developers commonly use two-way binding to keep component data in sync with a form control as a user interacts with the control. For example, when a user fills out a text input, it should update the state in the component.

The following example dynamically updates the `firstName` attribute on the page:

```
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  imports: [FormsModule],
  template: `
    <main>
      <h2>Hello {{ firstName }}!</h2>
      <input type="text" [(ngModel)]="firstName" />
    </main>
  `,
})
export class App {
  firstName = 'Ada';
}
```

To use two-way binding with native form controls, you need to:

1. Import the [`FormsModule`](/api/forms/FormsModule) from `@angular/forms`
2. Use the [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) directive with the two-way binding syntax (e.g., `[(ngModel)]`)
3. Assign it the state that you want it to update (e.g., `firstName`)

Once that is set up, Angular will ensure that any updates in the text input will reflect correctly inside of the component state!

Learn more about [`NgModel`](/api/forms/NgModel) in the official docs.

## [Two-way binding between components](#two-way-binding-between-components)

Leveraging two-way binding between a parent and child component requires more configuration compared to form elements.

Here is an example where the `App` is responsible for setting the initial count state, but the logic for updating and rendering the UI for the counter primarily resides inside its child `Counter`.

### app.ts

```
import {Component} from '@angular/core';
import {Counter} from './counter';

@Component({
  selector: 'app-root',
  imports: [Counter],
  template: `
    <main>
      <h1>Counter: {{ initialCount }}</h1>
      <app-counter [(count)]="initialCount"></app-counter>
    </main>
  `,
})
export class App {
  initialCount = 18;
}
```

### counter.ts

```
import {Component, model} from '@angular/core';

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="updateCount(-1)">-</button>
    <span>{{ count() }}</span>
    <button (click)="updateCount(+1)">+</button>
  `,
})
export class Counter {
  count = model<number>(0);

  updateCount(amount: number): void {
    this.count.update((currentCount) => currentCount + amount);
  }
}
```

### [Enabling two-way binding between components](#enabling-two-way-binding-between-components)

If we break down the example above to its core, each two-way binding for components requires the following:

The child component must contain a [`model`](/api/core/model) property.

Here is a simplified example:

### counter.ts

```
import {Component, model} from '@angular/core';

@Component({
  /* Omitted for brevity */
})
export class Counter {
  count = model<number>(0);

  updateCount(amount: number): void {
    this.count.update((currentCount) => currentCount + amount);
  }
}
```

The parent component must:

1. Wrap the [`model`](/api/core/model) property name in the two-way binding syntax.
2. Assign a property or a signal to the [`model`](/api/core/model) property.

Here is a simplified example:

### app.ts

```
import {Component} from '@angular/core';
import {Counter} from './counter';

@Component({
  selector: 'app-root',
  imports: [Counter],
  template: `
    <main>
      <app-counter [(count)]="initialCount"></app-counter>
    </main>
  `,
})
export class App {
  initialCount = 18;
}
```

----
url: https://angular.dev/api/core/EffectCleanupRegisterFn
----

```
    
      type EffectCleanupRegisterFn = (cleanupFn: EffectCleanupFn) => void
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/RootFieldContext
----

```
    
      interface RootFieldContext<TValue> {
}
    
    
```

A signal containing the value of the current field.

The state of the current field.

The current field.

Gets the value of the field represented by the given path.

@paramp`SchemaPath<PValue, SchemaPathRules, Root>`

@returns`PValue`

Gets the state of the field represented by the given path.

@paramp`CompatSchemaPath<PControl, Root>`

@returns`[PControl] extends [any] ? ReadonlyCompatFieldState<PControl, string | number> : never`

@paramp`SchemaPath<PValue, SchemaPathRules, Root>`

@returns`[PValue] extends [any] ? ReadonlyFieldState<PValue, string | number> : never`

Gets the field represented by the given path.

@paramp`SchemaPathTree<PModel, Root>`

@returns`[PModel] extends [any] ? ReadonlyFieldTree<PModel, string | number> : never`

The list of keys that lead from the root field to the current field.

Jump to details

----
url: https://angular.dev/errors/NG0100
----

[](https://www.youtube.com/watch?v=O47uUnJjbJc\&autoplay=1)

Angular throws an `ExpressionChangedAfterItHasBeenCheckedError` when an expression value has been changed after change detection has completed. Angular only throws this error in development mode.

In development mode, Angular performs an additional check after each change detection run, to ensure the bindings haven't changed. This catches errors where the view is left in an inconsistent state. This can occur, for example, if a method or getter returns a different value each time it is called, or if a child component changes values on its parent. If either of these occurs, this is a sign that change detection is not stabilized. Angular throws the error to ensure data is always reflected correctly in the view, which prevents erratic UI behavior or a possible infinite loop.

This error commonly occurs when you've added template expressions or have begun to implement lifecycle hooks like `ngAfterViewInit` or `ngOnChanges`. It is also common when dealing with loading status and asynchronous operations, or when a child component changes its parent bindings.

## [Debugging the error](#debugging-the-error)

The [source maps](https://developer.mozilla.org/docs/Tools/Debugger/How_to/Use_a_source_map) generated by the CLI are very useful when debugging. Navigate up the call stack until you find a template expression where the value displayed in the error has changed.

Ensure that there are no changes to the bindings in the template after change detection is run. This often means refactoring to use the correct component lifecycle hook for your use case. If the issue exists within `ngAfterViewInit`, the recommended solution is to use a constructor or `ngOnInit` to set initial values, or use `ngAfterContentInit` for other value bindings.

If you are binding to methods in the view, ensure that the invocation does not update any of the other bindings in the template.

Read more about which solution is right for you in ['Everything you need to know about the "ExpressionChangedAfterItHasBeenCheckedError" error'](https://medium.com/angular-in-depth/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4) and why this is useful at ['Angular Debugging "Expression has changed after it was checked": Simple Explanation (and Fix)'](https://blog.angular-university.io/angular-debugging).

----
url: https://angular.dev/tutorials/learn-angular/6-property-binding
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/5-control-flow-for)[](/tutorials/learn-angular/7-event-handling)

[](/tutorials/learn-angular/5-control-flow-for)[](/tutorials/learn-angular/7-event-handling)

Property binding in Angular enables you to set values for properties of HTML elements, Angular components and more.

Use property binding to dynamically set values for properties and attributes. You can do things such as toggle button features, set image paths programmatically, and share values between components.

**NOTE:** Learn more about [setting dynamic properties and attributes in the essentials guide](/essentials/templates#setting-dynamic-properties-and-attributes).

In this activity, you'll learn how to use property binding in templates.

***

To bind to an element's attribute, wrap the attribute name in square brackets. Here's an example:

```
<img alt="photo" [src]="imageURL" />
```

In this example, the value of the `src` attribute will be bound to the class property `imageURL`. Whatever value `imageURL` has will be set as the `src` attribute of the `img` tag.

1. ### [Add a property called `isEditable`](#add-a-property-called-iseditable)

   Update the code in `app.ts` by adding a property to the `App` class called `isEditable` with the initial value set to `true`.

   ```
   export class App {
     isEditable = true;
   }
   ```

2. ### [Bind to `contentEditable`](#bind-to-contenteditable)

   Next, bind the `contentEditable` attribute of the `div` to the `isEditable` property by using the `[]` syntax.

   ```
   @Component({
     ...
     template: `<div [contentEditable]="isEditable"></div>`,
   })
   ```

The div is now editable. Nice work 👍

Property binding is one of Angular's many powerful features. If you'd like to learn more checkout [the Angular documentation](guide/templates/binding#css-class-and-style-property-bindings).

----
url: https://angular.dev/api/platform-server/BEFORE_APP_SERIALIZED
----

```
    
      const BEFORE_APP_SERIALIZED: InjectionToken<readonly (() => void | Promise<void>)[]>;
    
    
```

Jump to details

----
url: https://angular.dev/cli/cache/clean
----

```
ng cache clean
```

Deletes persistent disk cache from disk.

Jump to details

----
url: https://angular.dev/api/core/NgModuleFactory
----

This class was mostly used as a part of ViewEngine-based JIT API and is no longer needed in Ivy JIT mode. Angular provides APIs that accept NgModule classes directly (such as [PlatformRef.bootstrapModule](api/core/PlatformRef#bootstrapModule) and [createNgModule](api/core/createNgModule)), consider switching to those APIs instead of using factory-based ones.

## [API](#api)

```
    
      abstract class NgModuleFactory<T> {}
    
    
```

@paramparentInjector`Injector | null`

@returns`NgModuleRef<T>`

Jump to details

----
url: https://angular.dev/api/cdk/testing/protractor/ProtractorElement
----

```
    
      class ProtractorElement implements TestElement {
}
    
    
```

@paramelement`ElementFinder`

@returns`ProtractorElement`

----
url: https://angular.dev/api/common/http/HttpContextToken
----

```
    
      class HttpContextToken<T> {}
    
    
```

@paramdefaultValue`() => T`

@returns`HttpContextToken<T>`

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/BrowserAnimationsModuleConfig
----

```
    
      interface BrowserAnimationsModuleConfig {}
    
    
```

Whether animations should be disabled. Passing this is identical to providing the [`NoopAnimationsModule`](/api/platform-browser/animations/NoopAnimationsModule), but it can be controlled based on a runtime value.

Jump to details

----
url: https://angular.dev/guide/aria/toolbar
----

A container for grouping related controls and actions with keyboard navigation, commonly used for text formatting, toolbars, and command panels.

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
}

.group {
  gap: 0.5rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 1.25rem;
  background-color: transparent;
  color: var(--primary-contrast);
}

[ngToolbarWidget]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngToolbarWidget]:active {
  background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent);
}

[ngToolbarWidget]:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngToolbarWidget][aria-pressed="true"],
[ngToolbarWidget][aria-checked="true"] {
  color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="material-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  display: flex;
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
  border-radius: 2rem;
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--full-contrast));
  background-color: color-mix(in srgb, var(--vivid-pink) 20%, transparent);
  transition:
    width 0.2s ease-in-out,
    background-color 0.15s ease-in-out,
    color 0.15s ease-in-out;
}

[ngToolbarWidget]:focus {
  outline-offset: 2px;
  outline: 2px solid var(--vivid-pink);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--full-contrast));
  color: var(--page-background);
}

[ngToolbarWidget][aria-checked='true'] {
  width: 4.5rem;
}

.group {
  display: flex;
  gap: 0.5rem;
}

.separator {
  width: 1px;
  margin: 0 1rem;
  height: calc(100% - 2rem);
  background-color: var(--quinary-contrast);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="retro-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 1rem;
}

.group {
  gap: 1rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1.25rem;
  color: var(--page-background);

  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngToolbarWidget]:focus,
[ngToolbarWidget]:hover {
  transform: translate(1px, 1px);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

[ngToolbarWidget]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--retro-button-color);
}
```

## [Usage](#usage)

Toolbar works best for grouping related controls that users access frequently. Consider using toolbar when:

* **Multiple related actions** - You have several controls that perform related functions (like text formatting buttons)
* **Keyboard efficiency matters** - Users benefit from quick keyboard navigation through arrow keys
* **Grouped controls** - You need to organize controls into logical sections with separators
* **Frequent access** - Controls are used repeatedly within a workflow

Avoid toolbar when:

* A simple button group is sufficient - For just 2-3 unrelated actions, individual buttons work better
* Controls aren't related - Toolbar implies a logical grouping; unrelated controls confuse users
* Complex nested navigation - Deep hierarchies are better served by menus or navigation components

## [Features](#features)

Angular's toolbar provides a fully accessible toolbar implementation with:

* **Keyboard Navigation** - Navigate widgets with arrow keys, Home, and End using roving tabindex, while controls retain native activation
* **Screen Reader Support** - Built-in ARIA attributes for assistive technologies
* **Widget Groups** - Organize related widgets like radio button groups or toggle button groups
* **Flexible Orientation** - Horizontal or vertical layouts with automatic keyboard navigation
* **Signal-Based Reactivity** - Reactive state management using Angular signals
* **Bidirectional Text Support** - Automatically handles right-to-left (RTL) languages
* **Configurable Focus** - Choose between wrapping navigation or hard stops at edges

## [Examples](#examples)

### [Basic horizontal toolbar](#basic-horizontal-toolbar)

Horizontal toolbars organize controls from left to right, matching the common pattern in text editors and design tools. Arrow keys navigate between widgets, maintaining focus within the toolbar until users press Tab to move to the next page element.

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
}

.group {
  gap: 0.5rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 1.25rem;
  background-color: transparent;
  color: var(--primary-contrast);
}

[ngToolbarWidget]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngToolbarWidget]:active {
  background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent);
}

[ngToolbarWidget]:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngToolbarWidget][aria-pressed="true"],
[ngToolbarWidget][aria-checked="true"] {
  color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="material-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  display: flex;
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
  border-radius: 2rem;
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--full-contrast));
  background-color: color-mix(in srgb, var(--vivid-pink) 20%, transparent);
  transition:
    width 0.2s ease-in-out,
    background-color 0.15s ease-in-out,
    color 0.15s ease-in-out;
}

[ngToolbarWidget]:focus {
  outline-offset: 2px;
  outline: 2px solid var(--vivid-pink);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--full-contrast));
  color: var(--page-background);
}

[ngToolbarWidget][aria-checked='true'] {
  width: 4.5rem;
}

.group {
  display: flex;
  gap: 0.5rem;
}

.separator {
  width: 1px;
  margin: 0 1rem;
  height: calc(100% - 2rem);
  background-color: var(--quinary-contrast);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="retro-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 1rem;
}

.group {
  gap: 1rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1.25rem;
  color: var(--page-background);

  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngToolbarWidget]:focus,
[ngToolbarWidget]:hover {
  transform: translate(1px, 1px);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

[ngToolbarWidget]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--retro-button-color);
}
```

### [Vertical toolbar](#vertical-toolbar)

Vertical toolbars stack controls top to bottom, useful for side panels or vertical command palettes. Up and down arrow keys navigate between widgets.

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
}
```

### app.html

```
<div ngToolbar orientation="vertical" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: flex-start;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  flex-direction: column;
  padding: 1rem 0.5rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
}

.group {
  gap: 0.5rem;
  display: flex;
  flex-direction: column;
}

.separator {
  height: 1px;
  align-self: center;
  width: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 1.25rem;
  background-color: transparent;
  color: var(--primary-contrast);
}

[ngToolbarWidget]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngToolbarWidget]:active {
  background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent);
}

[ngToolbarWidget]:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngToolbarWidget][aria-pressed="true"],
[ngToolbarWidget][aria-checked="true"] {
  color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
}
```

### app.html

```
<div
  ngToolbar
  orientation="vertical"
  class="vertical-material-toolbar"
  aria-label="Text Formatting Tools"
>
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: flex-start;
}

[ngToolbar] {
  display: flex;
  flex-direction: column;
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
  border-radius: 2rem;
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--full-contrast));
  background-color: color-mix(in srgb, var(--vivid-pink) 20%, transparent);
  transition:
    height 0.2s ease-in-out,
    background-color 0.15s ease-in-out,
    color 0.15s ease-in-out;
}

[ngToolbarWidget]:focus {
  outline-offset: 2px;
  outline: 2px solid var(--vivid-pink);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  background-color: var(--vivid-pink);
  color: var(--page-background);
}

[ngToolbarWidget][aria-checked='true'] {
  height: 4.5rem;
}

.group {
  gap: 0.5rem;
  display: flex;
  flex-direction: column;
}

.separator {
  width: 1px;
  margin: 1rem 0;
  height: calc(100% - 2rem);
  background-color: var(--quinary-contrast);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
}
```

### app.html

```
<div ngToolbar orientation="vertical" class="retro-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: flex-start;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 1rem;
  flex-direction: column;
}

.group {
  gap: 1rem;
  display: flex;
  flex-direction: column;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1.25rem;
  color: var(--page-background);

  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngToolbarWidget]:focus,
[ngToolbarWidget]:hover {
  transform: translate(1px, 1px);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

[ngToolbarWidget]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--retro-button-color);
}
```

### [Widget groups](#widget-groups)

Widget groups organize related controls that work together, such as text alignment options or formatting toggles. Groups maintain roving tabindex navigation while presenting the appropriate semantic structure to assistive technologies.

In the examples above, the alignment buttons are wrapped in [`ngToolbarWidgetGroup`](/api/aria/toolbar/ToolbarWidgetGroup) with `role="radiogroup"`. Selection is decoupled from the toolbar container, allowing you to manage state using Angular signals or custom directives:

```
<!-- Mutually exclusive radio group -->
<div ngToolbarWidgetGroup role="radiogroup" aria-label="Alignment">
  <button
    ngToolbarWidget
    role="radio"
    type="button"
    [attr.aria-checked]="alignment() === 'left'"
    (click)="alignment.set('left')"
  >
    Left
  </button>
  <button
    ngToolbarWidget
    role="radio"
    type="button"
    [attr.aria-checked]="alignment() === 'center'"
    (click)="alignment.set('center')"
  >
    Center
  </button>
  <button
    ngToolbarWidget
    role="radio"
    type="button"
    [attr.aria-checked]="alignment() === 'right'"
    (click)="alignment.set('right')"
  >
    Right
  </button>
</div>

<!-- Toggle button group -->
<div class="group" role="group" aria-label="Text styling">
  <button ngToolbarWidget type="button" [attr.aria-pressed]="bold()" (click)="bold.set(!bold())">
    Bold
  </button>
  <button
    ngToolbarWidget
    type="button"
    [attr.aria-pressed]="italic()"
    (click)="italic.set(!italic())"
  >
    Italic
  </button>
</div>
```

### [Disabled widgets](#disabled-widgets)

Toolbars support two disabled modes:

1. **Soft-disabled** widgets remain focusable but visually indicate they're unavailable
2. **Hard-disabled** widgets are completely removed from keyboard navigation.

By default, `softDisabled` is `true`, which allows disabled widgets to still receive focus. If you want to enable hard-disabled mode, set `[softDisabled]="false"` on the toolbar.

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      disabled
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div
    ngToolbarWidgetGroup
    role="radiogroup"
    class="group"
    aria-label="Text alignment options"
    disabled
  >
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
}

.group {
  gap: 0.5rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 1.25rem;
  background-color: transparent;
  color: var(--primary-contrast);
}

[ngToolbarWidget]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngToolbarWidget]:active {
  background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent);
}

[ngToolbarWidget]:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngToolbarWidget][aria-pressed="true"],
[ngToolbarWidget][aria-checked="true"] {
  color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}

[ngToolbarWidget][aria-disabled="true"] {
  cursor: default;
  opacity: 0.45;
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="disabled-material-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      disabled
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div
    ngToolbarWidgetGroup
    role="radiogroup"
    class="group"
    aria-label="Text alignment options"
    disabled
  >
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  display: flex;
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
  border-radius: 2rem;
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--full-contrast));
  background-color: color-mix(in srgb, var(--vivid-pink) 20%, transparent);
  transition:
    width 0.2s ease-in-out,
    background-color 0.15s ease-in-out,
    color 0.15s ease-in-out;
}

[ngToolbarWidget]:focus {
  outline-offset: 2px;
  outline: 2px solid var(--vivid-pink);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--full-contrast));
  color: var(--page-background);
}

[ngToolbarWidget][aria-checked='true'] {
  width: 4.5rem;
}

.group {
  display: flex;
  gap: 0.5rem;
}

.separator {
  width: 1px;
  margin: 0 1rem;
  height: calc(100% - 2rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget][aria-disabled="true"] {
  cursor: default;
  opacity: 0.45;
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="retro-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      disabled
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div
    ngToolbarWidgetGroup
    role="radiogroup"
    class="group"
    aria-label="Text alignment options"
    disabled
  >
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 1rem;
}

.group {
  gap: 1rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1.25rem;
  color: var(--page-background);

  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngToolbarWidget]:focus,
[ngToolbarWidget]:hover {
  transform: translate(1px, 1px);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

[ngToolbarWidget]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--retro-button-color);
}

[ngToolbarWidget][aria-disabled='true'] {
  cursor: default;
  opacity: 0.45;
}
```

### [Right-to-left (RTL) support](#right-to-left-rtl-support)

Toolbars automatically support right-to-left languages. Wrap the toolbar in a container with `dir="rtl"` to reverse the layout and keyboard navigation direction. Arrow key navigation adjusts automatically: left arrow moves to the next widget, right arrow to the previous.

### app.ts

```
import {Dir} from '@angular/cdk/bidi';
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Dir, Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar dir="rtl" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
}

.group {
  gap: 0.5rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 1.25rem;
  background-color: transparent;
  color: var(--primary-contrast);
}

[ngToolbarWidget]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngToolbarWidget]:active {
  background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent);
}

[ngToolbarWidget]:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngToolbarWidget][aria-pressed="true"],
[ngToolbarWidget][aria-checked="true"] {
  color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}
```

### app.ts

```
import {Dir} from '@angular/cdk/bidi';
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Dir, Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar dir="rtl" class="rtl-material-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  display: flex;
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
  border-radius: 2rem;
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--full-contrast));
  background-color: color-mix(in srgb, var(--vivid-pink) 20%, transparent);
  transition:
    width 0.2s ease-in-out,
    background-color 0.15s ease-in-out,
    color 0.15s ease-in-out;
}

[ngToolbarWidget]:focus {
  outline-offset: 2px;
  outline: 2px solid var(--vivid-pink);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  background-color: var(--vivid-pink);
  color: var(--page-background);
}

[ngToolbarWidget][aria-checked='true'] {
  width: 4.5rem;
}

.group {
  display: flex;
  gap: 0.5rem;
}

.separator {
  width: 1px;
  margin: 0 1rem;
  height: calc(100% - 2rem);
  background-color: var(--quinary-contrast);
}
```

### app.ts

```
import {Dir} from '@angular/cdk/bidi';
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Dir, Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar dir="rtl" class="rtl-retro-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 1rem;
}

.group {
  gap: 1rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1.25rem;
  color: var(--page-background);

  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngToolbarWidget]:focus,
[ngToolbarWidget]:hover {
  transform: translate(1px, 1px);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

[ngToolbarWidget]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--retro-button-color);
}
```

## [Testing](#testing)

Angular Aria provides component harnesses for testing toolbar components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ToolbarHarness} from '@angular/aria/toolbar/testing';
import {MyToolbarComponent} from './my-toolbar'; // Your component

describe('MyToolbarComponent', () => {
  let fixture: ComponentFixture<MyToolbarComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyToolbarComponent],
    });

    fixture = TestBed.createComponent(MyToolbarComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should have widgets and update toggle state on click', async () => {
    // Load the toolbar harness
    const toolbar = await loader.getHarness(ToolbarHarness);

    // Get all widgets
    const widgets = await toolbar.getWidgets();
    expect(widgets.length).toBe(3);

    // Click the first widget
    await widgets[0].click();

    // Verify pressed state updated via click handler
    expect(await widgets[0].isSelected()).toBe(true);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Toolbar`](/api/aria/toolbar/Toolbar)
* [`ToolbarWidget`](/api/aria/toolbar/ToolbarWidget)
* [`ToolbarWidgetGroup`](/api/aria/toolbar/ToolbarWidgetGroup)

----
url: https://angular.dev/tutorials/signals/5-component-communication-with-signals
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/4-managing-async-data-with-signals)[](/tutorials/signals/6-two-way-binding-with-model-signals)

[](/tutorials/signals/4-managing-async-data-with-signals)[](/tutorials/signals/6-two-way-binding-with-model-signals)

Now that you've learned [managing async data with signals](/tutorials/signals/4-managing-async-data-with-signals), let's explore Angular's signal-based [`input()`](/api/core/input) API for passing data from parent to child components, making component data flow more reactive and efficient. If you're familiar with component props from other frameworks, inputs are the same idea.

In this activity, you'll add signal inputs to a product card component and see how parent data flows down reactively.

***

1. ### [Add signal inputs to ProductCard](#add-signal-inputs-to-productcard)

   Add signal [`input()`](/api/core/input) functions to receive data in the `product-card` component.

   ```
   // Add imports for signal inputs
   import {Component, input, ChangeDetectionStrategy} from '@angular/core';

   // Add these signal inputs
   name = input.required<string>();
   price = input.required<number>();
   available = input<boolean>(true);
   ```

   Notice how [`input.required()`](/api/core/input) creates an input that must be provided, while [`input()`](/api/core/input) with a default value is optional.

2. ### [Connect inputs to the template](#connect-inputs-to-the-template)

   Update the template in `product-card` to display the signal input values.

   ```
   <div class="product-card">
     <h3>{{ name() }}</h3>
     <p class="price">\${{ price() }}</p>
     <p class="status">
       Status:
       @if (available()) {
         Available
       } @else {
         Out of Stock
       }
     </p>
   </div>
   ```

   Input signals work just like regular signals in templates - call them as functions to access their values.

3. ### [Connect parent signals to child inputs](#connect-parent-signals-to-child-inputs)

   Update the `product-card` usage in `app.ts` to pass dynamic signal values instead of static ones.

   ```
   <!-- Change from static values: -->
   <product-card name="Static Product" price="99" available="true" />

   <!-- To dynamic signals: -->
   <product-card [name]="productName()" [price]="productPrice()" [available]="productAvailable()" />
   ```

   The square brackets `[]` create property bindings that pass the current signal values to the child.

4. ### [Test reactive updates](#test-reactive-updates)

   Add methods in `app.ts` to update the parent signals and see how the child component reacts automatically.

   ```
   updateProduct() {
     this.productName.set(`Product ${Math.floor(Math.random() * 100)}`);
     this.productPrice.set(Math.floor(Math.random() * 500) + 50);
   }

   toggleAvailability() {
     this.productAvailable.set(!this.productAvailable());
   }
   ```

   ```
   <!-- Add controls to test reactivity -->
   <div class="controls">
     <button (click)="updateProduct()">Update Product Data</button>
     <button (click)="toggleAvailability()">Toggle Availability</button>
   </div>
   ```

   When parent signals change, the child component automatically receives and displays the new values!

Excellent! You've learned how signal inputs work:

* **Signal inputs** - Use [`input()`](/api/core/input) and [`input.required()`](/api/core/input) to receive data from parent components
* **Reactive updates** - Child components automatically update when parent signal values change
* **Type safety** - Signal inputs provide full TypeScript type checking
* **Default values** - Optional inputs can have default values while required inputs must be provided

Signal inputs make component communication more reactive and eliminate the need for [`OnChanges`](/api/core/OnChanges) lifecycle hooks in many cases.

In the next lesson, you'll learn about [two-way binding with model signals](/tutorials/signals/6-two-way-binding-with-model-signals)!

----
url: https://angular.dev/api/forms/FormControlDirective
----

```
    
      class FormControlDirective extends NgControl implements OnChanges ,OnDestroy {}
    
    
```

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@paramvalueAccessors`ControlValueAccessor[]`

@param\_ngModelWarningConfig`string | null`

@paramcallSetDisabledState`SetDisabledStateOption | undefined`

@paramrenderer`Renderer2 | undefined`

@paraminjector`Injector | undefined`

@returns`FormControlDirective`

Tracks the [`FormControl`](/api/forms/FormControl) instance bound to the directive.

Triggers a warning in dev mode that this input should not be used with reactive forms.

@deprecated

as of v6

@deprecated

as of v6

Returns an array that represents the path from the top-level form to this control. Each index is the string name of the control on that level.

The [`FormControl`](/api/forms/FormControl) bound to this directive.

Sets the new value for the view model and emits an `ngModelChange` event.

@paramnewValue`any`

The new value for the view model.

@returns`void`

The name for the control

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Synchronizes a standalone [`FormControl`](/api/forms/FormControl) instance to a form control element.

Note that support for using the `ngModel` input property and `ngModelChange` event with reactive form directives was deprecated in Angular v6 and is scheduled for removal in a future version of Angular.

## [Exported by](#exported-by)

* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

The following example shows how to register a standalone control and set its value.

```
import {Component} from '@angular/core';
import {FormControl, Validators} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <input [formControl]="control" />

    <p>Value: {{ control.value }}</p>
    <p>Validation status: {{ control.status }}</p>

    <button (click)="setValue()">Set value</button>
  `,
  standalone: false,
})
export class SimpleFormControl {
  control: FormControl = new FormControl('value', Validators.minLength(2));

  setValue() {
    this.control.setValue('new value');
  }
}
```

Jump to details

----
url: https://angular.dev/best-practices/security
----

This topic describes Angular's built-in protections against common web application vulnerabilities and attacks such as cross-site scripting attacks. It doesn't cover application-level security, such as authentication and authorization.

For more information about the attacks and mitigations described below, see the [Open Web Application Security Project (OWASP) Guide](https://www.owasp.org/index.php/Category:OWASP_Guide_Project).

[]()

### Reporting vulnerabilities

Angular is part of Google [Open Source Software Vulnerability Reward Program](https://bughunters.google.com/about/rules/6521337925468160/google-open-source-software-vulnerability-reward-program-rules). For vulnerabilities in Angular, please submit your report at [https://bughunters.google.com](https://bughunters.google.com/report).

For more information about how Google handles security issues, see [Google's security philosophy](https://www.google.com/about/appsecurity).

## [Best practices](#best-practices)

These are some best practices to ensure that your Angular application is secure.

1. **Keep current with the latest Angular library releases** - The Angular libraries get regular updates, and these updates might fix security defects discovered in previous versions. Check the Angular [change log](https://github.com/angular/angular/blob/main/CHANGELOG.md) for security-related updates.
2. **Don't alter your copy of Angular** - Private, customized versions of Angular tend to fall behind the current version and might not include important security fixes and enhancements. Instead, share your Angular improvements with the community and make a pull request.
3. **Avoid Angular APIs marked in the documentation as "*Security Risk*"** - For more information, see the [Trusting safe values](#trusting-safe-values) section of this page.

## [Preventing cross-site scripting (XSS)](#preventing-cross-site-scripting-xss)

[Cross-site scripting (XSS)](https://en.wikipedia.org/wiki/Cross-site_scripting) enables attackers to inject malicious code into web pages. Such code can then, for example, steal user and login data, or perform actions that impersonate the user. This is one of the most common attacks on the web.

To block XSS attacks, you must prevent malicious code from entering the Document Object Model (DOM). For example, if attackers can trick you into inserting a `<script>` tag in the DOM, they can run arbitrary code on your website. The attack isn't limited to `<script>` tags —many elements and properties in the DOM allow code execution, for example, `<img alt="" onerror="...">` and `<a href="javascript:...">`. If attacker-controlled data enters the DOM, expect security vulnerabilities.

### [Angular's cross-site scripting security model](#angulars-cross-site-scripting-security-model)

To systematically block XSS bugs, Angular treats all values as untrusted by default. When a value is inserted into the DOM from a template binding, or interpolation, Angular sanitizes and escapes untrusted values. If a value was already sanitized outside of Angular and is considered safe, communicate this to Angular by marking the [value as trusted](#trusting-safe-values).

Unlike values to be used for rendering, Angular templates are considered trusted by default, and should be treated as executable code. Never create templates by concatenating user input and template syntax. Doing this would enable attackers to [inject arbitrary code](https://en.wikipedia.org/wiki/Code_injection) into your application. To prevent these vulnerabilities, always use the default [Ahead-Of-Time (AOT) template compiler](#use-the-aot-template-compiler) in production deployments.

An extra layer of protection can be provided through the use of Content security policy and Trusted Types. These web platform features operate at the DOM level which is the most effective place to prevent XSS issues. Here they can't be bypassed using other, lower-level APIs. For this reason, it is strongly encouraged to take advantage of these features. To do this, configure the [content security policy](#content-security-policy) for the application and enable [trusted types enforcement](#enforcing-trusted-types).

### [Sanitization and security contexts](#sanitization-and-security-contexts)

*Sanitization* is the inspection of an untrusted value, turning it into a value that's safe to insert into the DOM. In many cases, sanitization doesn't change a value at all. Sanitization depends on a context. For example, a value that's harmless in CSS is potentially dangerous in a URL.

Angular defines the following security contexts:

| Security contexts | Details                                                                           |
| :---------------- | :-------------------------------------------------------------------------------- |
| HTML              | Used when interpreting a value as HTML, for example, when binding to `innerHtml`. |
| Style             | Used when binding CSS into the `style` property.                                  |
| URL               | Used for URL properties, such as `<a href>`.                                      |
| Resource URL      | A URL that is loaded and executed as code, for example, in `<script src>`.        |

Angular sanitizes untrusted values for HTML and URLs. Sanitizing resource URLs isn't possible because they contain arbitrary code. In development mode, Angular prints a console warning when it has to change a value during sanitization.

### [Sanitization example](#sanitization-example)

The following template binds the value of `htmlSnippet`. Once by interpolating it into an element's content, and once by binding it to the `innerHTML` property of an element:

### inner-html-binding.component.html

```
<h3>Binding innerHTML</h3>
<p>Bound value:</p>
<p class="e2e-inner-html-interpolated">{{ htmlSnippet }}</p>
<p>Result of binding to innerHTML:</p>
<p class="e2e-inner-html-bound" [innerHTML]="htmlSnippet"></p>
```

Interpolated content is always escaped —the HTML isn't interpreted and the browser displays angle brackets in the element's text content.

For the HTML to be interpreted, bind it to an HTML property such as `innerHTML`. Be aware that binding a value that an attacker might control into `innerHTML` normally causes an XSS vulnerability. For example, one could run JavaScript in a following way:

### inner-html-binding.component.ts (class)

```
export class InnerHtmlBindingComponent {
  // For example, a user/attacker-controlled value from a URL.
  htmlSnippet = 'Template <script>alert("0wned")</script> <b>Syntax</b>';
}
```

Angular recognizes the value as unsafe and automatically sanitizes it, which removes the `script` element but keeps safe content such as the `<b>` element.

### [Direct use of the DOM APIs and explicit sanitization calls](#direct-use-of-the-dom-apis-and-explicit-sanitization-calls)

Unless you enforce Trusted Types, the built-in browser DOM APIs don't automatically protect you from security vulnerabilities. For example, `document`, the node available through [`ElementRef`](/api/core/ElementRef), and many third-party APIs contain unsafe methods. Likewise, if you interact with other libraries that manipulate the DOM, you likely won't have the same automatic sanitization as with Angular interpolations. Avoid directly interacting with the DOM and instead use Angular templates where possible.

For cases where this is unavoidable, use the built-in Angular sanitization functions. Sanitize untrusted values with the [DomSanitizer.sanitize](api/platform-browser/DomSanitizer#sanitize) method and the appropriate [`SecurityContext`](/api/core/SecurityContext). That function also accepts values that were marked as trusted using the `bypassSecurityTrust` functions, and does not sanitize them, as [described below](#trusting-safe-values).

### [Trusting safe values](#trusting-safe-values)

Sometimes applications genuinely need to include executable code, display an `<iframe>` from some URL, or construct potentially dangerous URLs. To prevent automatic sanitization in these situations, tell Angular that you inspected a value, checked how it was created, and made sure it is secure. Do *be careful*. If you trust a value that might be malicious, you are introducing a security vulnerability into your application. If in doubt, find a professional security reviewer.

To mark a value as trusted, inject [`DomSanitizer`](/api/platform-browser/DomSanitizer) and call one of the following methods:

* `bypassSecurityTrustHtml`
* `bypassSecurityTrustScript`
* `bypassSecurityTrustStyle`
* `bypassSecurityTrustUrl`
* `bypassSecurityTrustResourceUrl`

Remember, whether a value is safe depends on context, so choose the right context for your intended use of the value. Imagine that the following template needs to bind a URL to a `javascript:alert(...)` call:

### bypass-security.component.html (URL)

```
<h4>An untrusted URL:</h4>
<p><a class="e2e-dangerous-url" [href]="dangerousUrl">Click me</a></p>
<h4>A trusted URL:</h4>
<p><a class="e2e-trusted-url" [href]="trustedUrl">Click me</a></p>
```

Normally, Angular automatically sanitizes the URL, disables the dangerous code, and in development mode, logs this action to the console. To prevent this, mark the URL value as a trusted URL using the `bypassSecurityTrustUrl` call:

### bypass-security.component.ts (trust-url)

```
private sanitizer = inject(DomSanitizer);
constructor() {
  // javascript: URLs are dangerous if attacker controlled.
  // Angular sanitizes them in data binding, but you can
  // explicitly tell Angular to trust this value:
  this.dangerousUrl = 'javascript:alert("Hi there")';
  this.trustedUrl = this.sanitizer.bypassSecurityTrustUrl(this.dangerousUrl);
```

If you need to convert user input into a trusted value, use a component method. The following template lets users enter a YouTube video ID and load the corresponding video in an `<iframe>`. The `<iframe src>` attribute is a resource URL security context, because an untrusted source can, for example, smuggle in file downloads that unsuspecting users could run. To prevent this, call a method on the component to construct a trusted video URL, which causes Angular to let binding into `<iframe src>`:

### bypass-security.component.html (iframe)

```
<h4>Resource URL:</h4>
<p>Showing: {{ dangerousVideoUrl }}</p>
<p>Trusted:</p>
<iframe
  class="e2e-iframe-trusted-src"
  width="640"
  height="390"
  [src]="videoUrl"
  title="trusted video url"
></iframe>
<p>Untrusted:</p>
<iframe
  class="e2e-iframe-untrusted-src"
  width="640"
  height="390"
  [src]="dangerousVideoUrl"
  title="unTrusted video url"
></iframe>
```

### bypass-security.component.ts (trust-video-url)

```
updateVideoUrl(id: string) {
  // Appending an ID to a YouTube URL is safe.
  // Always make sure to construct SafeValue objects as
  // close as possible to the input data so
  // that it's easier to check if the value is safe.
  this.dangerousVideoUrl = 'https://www.youtube.com/embed/' + id;
  this.videoUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.dangerousVideoUrl);
}
```

### [Content security policy](#content-security-policy)

Content Security Policy (CSP) is a defense-in-depth technique to prevent XSS. To enable CSP, configure your web server to return an appropriate `Content-Security-Policy` HTTP header. Read more about content security policy at the [Web Fundamentals guide](https://developers.google.com/web/fundamentals/security/csp) on the Google Developers website.

The minimal policy required for a brand-new Angular application is:

```
default-src 'self'; style-src 'self' 'nonce-randomNonceGoesHere'; script-src 'self' 'nonce-randomNonceGoesHere';
```

When serving your Angular application, the server should include a randomly-generated nonce in the HTTP header for each request. You must provide this nonce to Angular so that the framework can render `<style>` elements. You can set the nonce for Angular in one of the following ways:

1. Set the `ngCspNonce` attribute on the root application element as `<app ngCspNonce="randomNonceGoesHere"></app>`. Use this approach if you have access to server-side templating that can add the nonce both to the header and the `index.html` when constructing the response.
2. Provide the nonce using the [`CSP_NONCE`](/api/core/CSP_NONCE) injection token. Use this approach if you have access to the nonce at runtime and you want to be able to cache the `index.html`.

```
import {CSP_NONCE} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    {
      provide: CSP_NONCE,
      useValue: globalThis.myRandomNonceValue,
    },
  ],
});
```

### Unique nonces

Always ensure that the nonces you provide are **unique per request** and that they are not predictable or guessable. If an attacker can predict future nonces, they can circumvent the protections offered by CSP.

Generating a nonce at the origin server is generally discouraged when using a CDN, as responses are frequently cached. If the server generates a nonce and the CDN caches that HTML response, every subsequent visitor receives the same "unique" value, allowing an attacker to discover the static value and bypass CSP protections.

To maintain the "one-time-use" integrity of a nonce, it should ideally be generated at the Edge layer (e.g., CDN) just before the content is delivered to the user.

**NOTE:** If you want to [inline the critical CSS](/tools/cli/build#critical-css-inlining) of your application, you can not use the [`CSP_NONCE`](/api/core/CSP_NONCE) token, and should prefer the `security.autoCsp` option in the [workspace configuration](reference/configs/workspace-config#extra-build-and-test-options) or set the `ngCspNonce` attribute on the root application element.

If you cannot generate nonces in your project, you can allow inline styles by adding `'unsafe-inline'` to the `style-src` section of the CSP header.

| Sections                                         | Details                                                                                                                                                                                                                                                                                                                                                |
| :----------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `default-src 'self';`                            | Allows the page to load all its required resources from the same origin.                                                                                                                                                                                                                                                                               |
| `style-src 'self' 'nonce-randomNonceGoesHere';`  | Allows the page to load global styles from the same origin (`'self'`) and styles inserted by Angular with the `nonce-randomNonceGoesHere`.                                                                                                                                                                                                             |
| `script-src 'self' 'nonce-randomNonceGoesHere';` | Allows the page to load JavaScript from the same origin (`'self'`) and scripts inserted by the Angular CLI with the `nonce-randomNonceGoesHere`. Required only when you enable critical CSS inlining or subresource integrity (which injects an import map to validate dynamically imported chunks), both of which add inline scripts to `index.html`. |

Angular itself requires only these settings to function correctly. As your project grows, you may need to expand your CSP settings to accommodate extra features specific to your application.

#### [Static hosting without per-response nonces](#static-hosting-without-per-response-nonces)

If your hosting environment or CDN can replace a placeholder token in cached HTML at the edge (for example, using SSI, ESI, or an edge function), you can build `index.html` with a placeholder in `ngCspNonce` (such as `<app ngCspNonce="__CSP_NONCE__"></app>`) and replace it with a unique nonce on each response.

If you deploy your application to a static host that serves `index.html` unchanged without edge transformation, do not hard-code a static nonce. Instead, use one of the following approaches:

##### [Hash inline scripts with `autoCsp`](#hash-inline-scripts-with-autocsp)

Set the `security.autoCsp` option to `true` in the [workspace configuration](reference/configs/workspace-config#extra-build-and-test-options). At build time, the Angular CLI computes a hash of every inline script in `index.html`, including the scripts added by critical CSS inlining and subresource integrity. The CLI replaces the `<script src>` elements with a hashed loader script and adds a `<meta>` tag at the start of `<head>`:

```
script-src 'strict-dynamic' 'sha256-...' https: 'unsafe-inline'; object-src 'none'; base-uri 'self';
```

Because hashes depend only on the content of the scripts, `index.html` remains valid for every visitor and can be cached. Browsers that support hashes and `'strict-dynamic'` ignore the `https:` and `'unsafe-inline'` fallback sources.

The policy generated by `autoCsp` has the following limitations:

* It only covers scripts. You must configure `style-src` separately.
* Browsers ignore some directives, such as `frame-ancestors`, `report-uri`, and `sandbox`, when they appear in a `<meta>` tag. Send these directives in a `Content-Security-Policy` HTTP header.
* When a page has multiple policies, the browser enforces all of them. If you also send a CSP header, omit both `script-src` and `default-src` from the header so they do not block the hashed inline scripts.
* You cannot use `autoCsp` with server-side rendering.

##### [Avoid inline scripts](#avoid-inline-scripts)

The Angular CLI only adds inline scripts to `index.html` for critical CSS inlining and subresource integrity (which emits an import map to carry integrity metadata for dynamic module imports). If you set `optimization.styles.inlineCritical` to `false` and leave `subresourceIntegrity` disabled, `index.html` does not contain inline scripts. You can then configure a `Content-Security-Policy` header with `script-src 'self'`.

**NOTE:** Disabling critical CSS inlining can slow down the initial render of your application, and disabling subresource integrity removes script integrity checks.

##### [Configure styles for static hosting](#configure-styles-for-static-hosting)

Angular inserts `<style>` elements for component styles at runtime, and critical CSS inlining adds a `<style>` element to `index.html`. Neither `autoCsp` nor disabling inline scripts covers these styles. Without a per-response nonce, allow them by adding `'unsafe-inline'` to `style-src`. For example, if your build avoids inline scripts, configure your host to send the following header:

```
default-src 'self'; style-src 'self' 'unsafe-inline';
```

If you use `autoCsp`, send `style-src 'self' 'unsafe-inline'` in the HTTP header without `default-src` or `script-src`.

Your application code and third-party libraries might require additional directives in both cases.

### [Enforcing Trusted Types](#enforcing-trusted-types)

It is recommended that you use [Trusted Types](https://w3c.github.io/trusted-types/dist/spec/) as a way to help secure your applications from cross-site scripting attacks. Trusted Types is a [web platform](https://en.wikipedia.org/wiki/Web_platform) feature that can help you prevent cross-site scripting attacks by enforcing safer coding practices. Trusted Types can also help simplify the auditing of application code.

### Trusted types

Trusted Types might not yet be available in all browsers your application targets. In the case your Trusted-Types-enabled application runs in a browser that doesn't support Trusted Types, the features of the application are preserved. Your application is guarded against XSS by way of Angular's DomSanitizer. See [caniuse.com/trusted-types](https://caniuse.com/trusted-types) for the current browser support.

To enforce Trusted Types for your application, you must configure your application's web server to emit HTTP headers with one of the following Angular policies:

| Policies                 | Detail                                                                                                                                                                                                                                                                                     |
| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `angular`                | This policy is used in security-reviewed code that is internal to Angular, and is required for Angular to function when Trusted Types are enforced. Any inline template values or content sanitized by Angular is treated as safe by this policy.                                          |
| `angular#bundler`        | This policy is used by the Angular CLI bundler when creating lazy chunk files.                                                                                                                                                                                                             |
| `angular#unsafe-bypass`  | This policy is used for applications that use any of the methods in Angular's [DomSanitizer](api/platform-browser/DomSanitizer) that bypass security, such as `bypassSecurityTrustHtml`. Any application that uses these methods must enable this policy.                                  |
| `angular#unsafe-jit`     | This policy is used by the [Just-In-Time (JIT) compiler](api/core/Compiler). You must enable this policy if your application interacts directly with the JIT compiler or is running in JIT mode using the [platform browser dynamic](api/platform-browser-dynamic/platformBrowserDynamic). |
| `angular#unsafe-upgrade` | This policy is used by the [@angular/upgrade](api/upgrade/static/UpgradeModule) package. You must enable this policy if your application is an AngularJS hybrid.                                                                                                                           |

You should configure the HTTP headers for Trusted Types in the following locations:

* Production serving infrastructure
* Angular CLI (`ng serve`), using the `headers` property in the `angular.json` file, for local development and end-to-end testing
* Karma (`ng test`), using the `customHeaders` property in the `karma.config.js` file, for unit testing

The following is an example of a header specifically configured for Trusted Types and Angular:

```
Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';
```

An example of a header specifically configured for Trusted Types and Angular applications that use any of Angular's methods in [DomSanitizer](api/platform-browser/DomSanitizer) that bypasses security:

```
Content-Security-Policy: trusted-types angular angular#unsafe-bypass; require-trusted-types-for
'script';
```

The following is an example of a header specifically configured for Trusted Types and Angular applications using JIT:

```
Content-Security-Policy: trusted-types angular angular#unsafe-jit; require-trusted-types-for
'script';
```

The following is an example of a header specifically configured for Trusted Types and Angular applications that use lazy loading of modules:

```
Content-Security-Policy: trusted-types angular angular#bundler; require-trusted-types-for 'script';
```

### Community contributions

To learn more about troubleshooting Trusted Type configurations, the following resource might be helpful:

[Prevent DOM-based cross-site scripting vulnerabilities with Trusted Types](https://web.dev/trusted-types/#how-to-use-trusted-types)

### [Use the AOT template compiler](#use-the-aot-template-compiler)

The AOT template compiler prevents a whole class of vulnerabilities called template injection, and greatly improves application performance. The AOT template compiler is the default compiler used by Angular CLI applications, and you should use it in all production deployments.

An alternative to the AOT compiler is the JIT compiler which compiles templates to executable template code within the browser at runtime. Angular trusts template code, so dynamically generating templates and compiling them, in particular templates containing user data, circumvents Angular's built-in protections. This is a security anti-pattern. For information about dynamically constructing forms in a safe way, see the [Dynamic Forms](guide/forms/dynamic-forms) guide.

### [Server-side XSS protection](#server-side-xss-protection)

HTML constructed on the server is vulnerable to injection attacks. Injecting template code into an Angular application is the same as injecting executable code into the application: It gives the attacker full control over the application. To prevent this, use a templating language that automatically escapes values to prevent XSS vulnerabilities on the server. Don't create Angular templates on the server side using a templating language. This carries a high risk of introducing template-injection vulnerabilities.

## [HTTP-level vulnerabilities](#http-level-vulnerabilities)

Angular has built-in support to help prevent two common HTTP vulnerabilities, cross-site request forgery (CSRF or XSRF) and cross-site script inclusion (XSSI). Both of these must be mitigated primarily on the server side, but Angular provides helpers to make integration on the client side easier.

### [Cross-site request forgery](#cross-site-request-forgery)

In a cross-site request forgery (CSRF or XSRF), an attacker tricks the user into visiting a different web page (such as `evil.com`) with malignant code. This web page secretly sends a malicious request to the application's web server (such as `example-bank.com`).

Assume the user is logged into the application at `example-bank.com`. The user opens an email and clicks a link to `evil.com`, which opens in a new tab.

The `evil.com` page immediately sends a malicious request to `example-bank.com`. Perhaps it's a request to transfer money from the user's account to the attacker's account. The browser automatically sends the `example-bank.com` cookies, including the authentication cookie, with this request.

If the `example-bank.com` server lacks XSRF protection, it can't tell the difference between a legitimate request from the application and the forged request from `evil.com`.

To prevent this, the application must ensure that a user request originates from the real application, not from a different site. The server and client must cooperate to thwart this attack.

In a common anti-XSRF technique, the application server sends a randomly created authentication token in a cookie. The client code reads the cookie and adds a custom request header with the token in all following requests. The server compares the received cookie value to the request header value and rejects the request if the values are missing or don't match.

This technique is effective because all browsers implement the *same origin policy*. Only code from the website on which cookies are set can read the cookies from that site and set custom headers on requests to that site. That means only your application can read this cookie token and set the custom header. The malicious code on `evil.com` can't.

### [`HttpClient` XSRF/CSRF security](#httpclient-xsrf-csrf-security)

[`HttpClient`](/api/common/http/HttpClient) supports a [common mechanism](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Cookie-to-header_token) used to prevent XSRF attacks. When performing HTTP requests, an interceptor reads a token from a cookie, by default `XSRF-TOKEN`, and sets it as an HTTP header, `X-XSRF-TOKEN`. Because only code that runs on your domain could read the cookie, the backend can be certain that the HTTP request came from your client application and not an attacker.

By default, an interceptor sends this header on all mutating requests (such as `POST`) to relative and same origin URLs, but not on `GET` or `HEAD` requests.

### Why not protect GET requests?

CSRF protection is only needed for requests that can change state on the backend. By their nature, CSRF attacks cross domain boundaries, and the web's [same-origin policy](https://developer.mozilla.org/docs/Web/Security/Same-origin_policy) will prevent an attacking page from retrieving the results of authenticated `GET` requests.

To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called `XSRF-TOKEN` on either the page load or the first GET request. On subsequent requests the server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be sure that only code running on your domain could have sent the request. The token must be unique for each user and must be verifiable by the server; this prevents the client from making up its own tokens. Set the token to a digest of your site's authentication cookie with a salt for added security.

To prevent collisions in environments where multiple Angular apps share the same domain or subdomain, give each application a unique cookie name.

### HttpClient supports only the client half of the XSRF protection scheme

Your backend service must be configured to set the cookie for your page, and to verify that the header is present on all eligible requests. Failing to do so renders Angular's default protection ineffective.

### [Configure custom cookie/header names](#configure-custom-cookie-header-names)

If your backend service uses different names for the XSRF token cookie or header, use [`withXsrfConfiguration`](/api/common/http/withXsrfConfiguration) to override the defaults.

Add it to the [`provideHttpClient`](/api/common/http/provideHttpClient) call as follows:

```
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withXsrfConfiguration({
        cookieName: 'CUSTOM_XSRF_TOKEN',
        headerName: 'X-Custom-Xsrf-Header',
      }),
    ),
  ],
};
```

### [Disabling XSRF protection](#disabling-xsrf-protection)

If the built-in XSRF protection mechanism doesn't work for your application, you can disable it using the [`withNoXsrfProtection`](/api/common/http/withNoXsrfProtection) feature:

```
export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient(withNoXsrfProtection())],
};
```

For information about CSRF at the Open Web Application Security Project (OWASP), see [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) and [Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html). The Stanford University paper [Robust Defenses for Cross-Site Request Forgery](https://seclab.stanford.edu/websec/csrf/csrf.pdf) is a rich source of detail.

See also Dave Smith's [talk on XSRF at AngularConnect 2016](https://www.youtube.com/watch?v=9inczw6qtpY "Cross Site Request Funkery Securing Your Angular Apps From Evil Doers").

### [Cross-site script inclusion (XSSI)](#cross-site-script-inclusion-xssi)

Cross-site script inclusion, also known as JSON vulnerability, can allow an attacker's website to read data from a JSON API. The attack works on older browsers by overriding built-in JavaScript object constructors, and then including an API URL using a `<script>` tag.

This attack is only successful if the returned JSON is executable as JavaScript. Servers can prevent an attack by prefixing all JSON responses to make them non-executable, by convention, using the well-known string `")]}',\n"`.

Angular's [`HttpClient`](/api/common/http/HttpClient) library recognizes this convention and automatically strips the string `")]}',\n"` from all responses before further parsing.

For more information, see the XSSI section of this [Google web security blog post](https://security.googleblog.com/2011/05/website-security-for-webmasters.html).

## [Preventing Server-Side Request Forgery (SSRF)](#preventing-server-side-request-forgery-ssrf)

Angular includes strict validation for `Host`, `Forwarded`, `X-Forwarded-Host`, `X-Forwarded-Proto`, `X-Forwarded-Prefix`, and `X-Forwarded-Port` headers in the request handling pipeline to prevent header-based [Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/SSRF).

The validation rules are:

* `Host`, `X-Forwarded-Host`, and the `host` parameter of the `Forwarded` header are validated against a strict allowlist and cannot contain path separators.
* `X-Forwarded-Port` header must be numeric.
* `X-Forwarded-Proto` header and the `proto` parameter of the `Forwarded` header must be `http` or `https`.
* `X-Forwarded-Prefix` header must start with `/` and contain only alphanumeric characters, hyphens, and underscores, separated by single slashes.
* By default, the `Forwarded` header and all `X-Forwarded-*` headers are treated as untrusted and are removed from the request. To retain them, they must be explicitly allowed by configuring `trustProxyHeaders`.

Invalid headers trigger an error log, and unallowed proxy headers are removed from the request. Requests with unrecognized hostnames will result in a `400 Bad Request`.

**NOTE:** Most cloud providers and CDN providers perform automatic validation of these headers before a request ever reaches the application origin. This inherent filtering significantly reduces the practical attack surface.

### [Configuring allowed hosts](#configuring-allowed-hosts)

To allow specific hostnames, you need to add them to the allowlist. This is critical for ensuring your application works correctly and securely when deployed. The patterns support wildcards for flexible hostname matching.

You can configure the `allowedHosts` option in your `angular.json`:

```
{
  // ...
  "projects": {
    "your-project-name": {
      // ...
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "security": {
              "allowedHosts": [
                "example.com",
                "*.example.com" // allows all subdomains of example.com
              ]
            }
            // ... other options
          }
        }
      }
    }
  }
}
```

You can also configure `allowedHosts` when initializing the application engine:

```
const appEngine = new AngularAppEngine({
  allowedHosts: ['example.com', '*.trusted-example.com'],
});

const nodeAppEngine = new AngularNodeAppEngine({
  allowedHosts: ['example.com', '*.trusted-example.com'],
});
```

For the Node.js variant [`AngularNodeAppEngine`](/api/ssr/node/AngularNodeAppEngine), you can also provide `NG_ALLOWED_HOSTS` (comma-separated list) environment variable for authorizing hosts.

```
export NG_ALLOWED_HOSTS="example.com,*.trusted-example.com"
```

**IMPORTANT:** You can use `*` as a value in `allowedHosts` to allow all hostnames, though this is generally discouraged and presents a security risk. Accepting any host header can expose your application to host header injection and [Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/SSRF) attacks. This configuration should only be used when validation for `Host` and `X-Forwarded-Host` headers is performed in another layer, such as a load balancer or reverse proxy. For better security, we recommend using an explicit list of allowed hosts whenever possible. See [GHSA-x288-3778-4hhx](https://github.com/angular/angular-cli/security/advisories/GHSA-x288-3778-4hhx) for more details.

### [Configuring trusted proxy headers](#configuring-trusted-proxy-headers)

By default, Angular ignores the standard `Forwarded` header and all `X-Forwarded-*` headers. If your application is behind a trusted reverse proxy (like a load balancer) that sets these headers, you can configure Angular to trust them.

If the `Forwarded` header is trusted, its `host` and `proto` parameters are extracted and take precedence over the corresponding `x-forwarded-host` and `x-forwarded-proto` headers.

You can configure `trustProxyHeaders` when initializing the application engine:

```
const appEngine = new AngularAppEngine({
  trustProxyHeaders: ['forwarded'], // Trust the standard Forwarded header
});

const appEngine = new AngularAppEngine({
  trustProxyHeaders: ['x-forwarded-host', 'x-forwarded-proto'], // Trust non-standard headers
});

const nodeAppEngine = new AngularNodeAppEngine({
  trustProxyHeaders: true, // Trust standard Forwarded and all X-Forwarded-* headers
});
```

For the Node.js variant [`AngularNodeAppEngine`](/api/ssr/node/AngularNodeAppEngine), you can also provide the `NG_TRUST_PROXY_HEADERS` environment variable (with a comma-separated list of headers as a value) to allow the usage of these headers.

```
export NG_TRUST_PROXY_HEADERS="X-FORWARDED-HOST,X-FORWARDED-PREFIX"
```

**IMPORTANT:** Only enable `trustProxyHeaders` if your application is behind a trusted proxy that strictly validates or overrides these headers. Otherwise, attackers can spoof these headers to cause [Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/SSRF) attacks.

## [Auditing Angular applications](#auditing-angular-applications)

Angular applications must follow the same security principles as regular web applications, and must be audited as such. Angular-specific APIs that should be audited in a security review, such as the [*bypassSecurityTrust*](#trusting-safe-values) methods, are marked in the documentation as security sensitive.

----
url: https://angular.dev/errors/NG8024
----

Two or more host directive declarations expose the **same input or output under different aliases**, making it impossible for Angular to merge the duplicate host directive instances.

This error arises from the [diamond composition pattern](guide/directives/directive-composition-api#host-directive-de-duplication): two directives both declare the same shared directive in their `hostDirectives`, but each exposes one of its bindings under a different alias name.

```
@Directive()
export class Shared {
  readonly value = input('');
}

@Directive({
  selector: '[dirA]',
  hostDirectives: [{directive: Shared, inputs: ['value: aliasA']}],
})
export class DirA {}

@Directive({
  selector: '[dirB]',
  hostDirectives: [{directive: Shared, inputs: ['value: aliasB']}], // different alias!
})
export class DirB {}

@Component({
  selector: 'app-root',
  // NG8024: Shared.value is exposed as both "aliasA" (via DirA) and "aliasB" (via DirB).
  template: `<div dirA dirB></div>`,
  imports: [DirA, DirB],
})
export class AppComponent {}
```

## [Debugging the error](#debugging-the-error)

The error message identifies the directive and the input or output that is conflicting. Find every `hostDirectives` declaration that includes that shared directive and inspect the `inputs` or `outputs` arrays for the conflicting binding.

To resolve the error, pick one of the following approaches:

* **Use the same alias in both places.** Both host directive declarations must expose the binding under the same public name.
* **Remove the alias from one or both declarations.** If neither declaration needs to expose the binding to consumers, omit it from the `inputs` or `outputs` arrays entirely.

See [Host directive de-duplication](guide/directives/directive-composition-api#host-directive-de-duplication) for more information on how Angular merges duplicate host directives and what constitutes a conflict.

----
url: https://angular.dev/tutorials/learn-angular/7-event-handling
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/6-property-binding)[](/tutorials/learn-angular/8-input)

[](/tutorials/learn-angular/6-property-binding)[](/tutorials/learn-angular/8-input)

Event handling enables interactive features on web apps. It gives you the ability as a developer to respond to user actions like button presses, form submissions and more.

**NOTE:** Learn more about [handling user interaction in the essentials guide](/essentials/templates#handling-user-interaction).

In this activity, you'll learn how to add an event handler.

***

In Angular you bind to events with the parentheses syntax `()`. On a given element, wrap the event you want to bind to with parentheses and set an event handler. Consider this `button` example:

```
@Component({
  ...
  template: `<button (click)="greet()">`
})
export class App {
  greet() {
    console.log('Hello, there 👋');
  }
}
```

In this example, the `greet()` function will run every time the button is clicked. Take note that the `greet()` syntax includes the trailing parenthesis.

Alright, your turn to give this a try:

1. ### [Add an event handler](/tutorials/learn-angular/7-event-handling#add-an-event-handler)

   In the `App` class, fill in the body of the existing `showSecretMessage()` method with the following code:

   ```
   this.message = 'Way to go 🚀';
   ```

2. ### [Bind to the template event](/tutorials/learn-angular/7-event-handling#bind-to-the-template-event)

   Update the template code in `app.ts` to bind to the `mouseover` event of the `section` element.

   ```
   <section (mouseover)="showSecretMessage()">
   ```

And with a few steps in the code you've created your first event handler in Angular. Seems like you are getting pretty good at this, keep up the good work.

----
url: https://angular.dev/api/forms/signals/MaxDateValidationError
----

```
    
      class MaxDateValidationError extends BaseNgValidationError {}
    
    
```

@parammaxDate`Date`

@paramoptions`ValidationErrorOptions | undefined`

@returns`MaxDateValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/router/NavigationStart
----

```
    
      class NavigationStart extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramnavigationTrigger`NavigationTrigger`

@paramrestoredState`{ [k: string]: any; navigationId: number; } | null`

@returns`NavigationStart`

Identifies the call or event that triggered the navigation. An `imperative` trigger is a call to `router.navigateByUrl()` or `router.navigate()`.

The navigation state that was previously supplied to the `pushState` call, when the navigation is triggered by a `popstate` event. Otherwise null.

The state object is defined by [`NavigationExtras`](/api/router/NavigationExtras), and contains any developer-defined state value, as well as a unique ID that the router assigns to every router transition/navigation.

From the perspective of the router, the router never "goes back". When the user clicks on the back button in the browser, a new navigation ID is created.

Use the ID in this previous-state object to differentiate between a newly created state and one returned to by a `popstate` event, so that you can restore some remembered state, such as scroll position.

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/api/router/Scroll
----

```
    
      class Scroll {}
    
    
```

@paramrouterEvent`NavigationEnd | NavigationSkipped`

@paramposition`[number, number] | null`

@paramanchor`string | null`

@paramscrollBehavior`"manual" | "after-transition" | undefined`

@returns`Scroll`

Jump to details

----
url: https://angular.dev/guide/routing/lifecycle-and-events
----

Angular Router provides a comprehensive set of lifecycle hooks and events that allow you to respond to navigation changes and execute custom logic during the routing process.

## [Common router events](#common-router-events)

The Angular Router emits navigation events that you can subscribe to in order to track the navigation lifecycle. These events are available through the [`Router.events`](/api/router/Router#events) observable. This section covers common routing lifecycle events for navigation and error tracking (in chronological order).

| Events                                              | Description                                                                                              |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| [`NavigationStart`](api/router/NavigationStart)     | Occurs when navigation begins and contains the requested URL.                                            |
| [`RoutesRecognized`](api/router/RoutesRecognized)   | Occurs after the router determines which route matches the URL and contains the route state information. |
| [`GuardsCheckStart`](api/router/GuardsCheckStart)   | Begins the route guard phase. The router evaluates route guards like `canActivate` and `canDeactivate`.  |
| [`GuardsCheckEnd`](api/router/GuardsCheckEnd)       | Signals completion of guard evaluation. Contains the result (allowed/denied).                            |
| [`ResolveStart`](api/router/ResolveStart)           | Begins the data resolution phase. Route resolvers and resources start fetching data.                     |
| [`ResolveEnd`](api/router/ResolveEnd)               | Data resolution completes. All required data and blocking resources become available.                    |
| [`NavigationEnd`](api/router/NavigationEnd)         | Final event when navigation completes successfully. The router updates the URL.                          |
| [`NavigationSkipped`](api/router/NavigationSkipped) | Occurs when the router skips navigation (e.g., same URL navigation).                                     |

The following are common error events:

| Event                                             | Description                                                                                                    |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [`NavigationCancel`](api/router/NavigationCancel) | Occurs when the router cancels navigation. Often due to a guard returning false.                               |
| [`NavigationError`](api/router/NavigationError)   | Occurs when navigation fails. Could be due to invalid routes, resolver errors, or rejected blocking resources. |

For a list of all lifecycle events, check out the [complete table of this guide](#all-router-events).

## [How to subscribe to router events](#how-to-subscribe-to-router-events)

When you want to run code during specific navigation lifecycle events, you can do so by subscribing to the `router.events` and checking the instance of the event:

```
// Example of subscribing to router events
import {Component, inject, signal, effect} from '@angular/core';
import {Event, Router, NavigationStart, NavigationEnd} from '@angular/router';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';

@Component(/* ... */)
export class RouterEvents {
  private readonly router = inject(Router);

  constructor() {
    // Subscribe to router events and react to events
    this.router.events.pipe(takeUntilDestroyed()).subscribe((event: Event) => {
      if (event instanceof NavigationStart) {
        // Navigation starting
        console.log('Navigation starting:', event.url);
      }
      if (event instanceof NavigationEnd) {
        // Navigation completed
        console.log('Navigation completed:', event.url);
      }
    });
  }
}
```

**NOTE:** The [`Event`](api/router/Event) type from `@angular/router` is named the same as the regular global [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event) type, but it is different from the [`RouterEvent`](api/router/RouterEvent) type.

## [How to debug routing events](#how-to-debug-routing-events)

Debugging router navigation issues can be challenging without visibility into the event sequence. Angular provides a built-in debugging feature that logs all router events to the console, helping you understand the navigation flow and identify where issues occur.

When you need to inspect a Router event sequence, you can enable logging for internal navigation events for debugging. You can configure this by passing a configuration option ([`withDebugTracing()`](/api/router/withDebugTracing)) that enables detailed console logging of all routing events.

```
import {provideRouter, Routes, withDebugTracing} from '@angular/router';

const appRoutes: Routes = [];
bootstrapApplication(App, {
  providers: [provideRouter(appRoutes, withDebugTracing())],
});
```

For more information, check out the official docs on [`withDebugTracing`](api/router/withDebugTracing).

## [Common use cases](#common-use-cases)

Router events enable many practical features in real-world applications. Here are some common patterns that are used with router events.

### [Loading indicators](#loading-indicators)

Show loading indicators during navigation:

```
import {Component, computed, inject} from '@angular/core';
import {Router} from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    @if (isNavigating()) {
      <div class="loading-bar">Loading...</div>
    }
    <router-outlet />
  `,
})
export class App {
  private router = inject(Router);
  isNavigating = computed(() => !!this.router.currentNavigation());
}
```

### [Analytics tracking](#analytics-tracking)

Track page views for analytics:

```
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {inject, DestroyRef, Service} from '@angular/core';
import {Router, NavigationEnd} from '@angular/router';

@Service()
export class AnalyticsService {
  private router = inject(Router);
  private destroyRef = inject(DestroyRef);

  startTracking() {
    this.router.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((event) => {
      // Track page views when URL changes
      if (event instanceof NavigationEnd) {
        // Send page view to analytics
        this.analytics.trackPageView(event.url);
      }
    });
  }

  private analytics = {
    trackPageView: (url: string) => {
      console.log('Page view tracked:', url);
    },
  };
}
```

### [Error handling](#error-handling)

Handle navigation errors gracefully and provide user feedback:

```
import {Component, inject, signal} from '@angular/core';
import {
  Router,
  NavigationStart,
  NavigationError,
  NavigationCancel,
  NavigationCancellationCode,
} from '@angular/router';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-error-handler',
  template: `
    @if (errorMessage()) {
      <div class="error-banner">
        {{ errorMessage() }}
        <button (click)="dismissError()">Dismiss</button>
      </div>
    }
  `,
})
export class ErrorHandler {
  private router = inject(Router);
  readonly errorMessage = signal('');

  constructor() {
    this.router.events.pipe(takeUntilDestroyed()).subscribe((event) => {
      if (event instanceof NavigationStart) {
        this.errorMessage.set('');
      } else if (event instanceof NavigationError) {
        console.error('Navigation error:', event.error);
        this.errorMessage.set('Failed to load page. Please try again.');
      } else if (event instanceof NavigationCancel) {
        console.warn('Navigation cancelled:', event.reason);
        if (event.code === NavigationCancellationCode.GuardRejected) {
          this.errorMessage.set('Access denied. Please check your permissions.');
        }
      }
    });
  }

  dismissError() {
    this.errorMessage.set('');
  }
}
```

## [All router events](#all-router-events)

For reference, here is the complete list of all router events available in Angular. These events are organized by category and listed in the order they typically occur during navigation.

### [Navigation events](#navigation-events)

These events track the core navigation process from start through route recognition, guard checks, and data resolution. They provide visibility into each phase of the navigation lifecycle.

| Event                                                     | Description                                                     |
| --------------------------------------------------------- | --------------------------------------------------------------- |
| [`NavigationStart`](api/router/NavigationStart)           | Occurs when navigation starts                                   |
| [`RouteConfigLoadStart`](api/router/RouteConfigLoadStart) | Occurs before lazy loading a route configuration                |
| [`RouteConfigLoadEnd`](api/router/RouteConfigLoadEnd)     | Occurs after a lazy-loaded route configuration loads            |
| [`RoutesRecognized`](api/router/RoutesRecognized)         | Occurs when the router parses the URL and recognizes the routes |
| [`GuardsCheckStart`](api/router/GuardsCheckStart)         | Occurs at the start of the guard phase                          |
| [`GuardsCheckEnd`](api/router/GuardsCheckEnd)             | Occurs at the end of the guard phase                            |
| [`ResolveStart`](api/router/ResolveStart)                 | Occurs at the start of the resolve phase                        |
| [`ResolveEnd`](api/router/ResolveEnd)                     | Occurs at the end of the resolve phase                          |

### [Activation events](#activation-events)

These events occur during the activation phase when route components are being instantiated and initialized. Activation events fire for each route in the route tree, including parent and child routes.

| Event                                                     | Description                                   |
| --------------------------------------------------------- | --------------------------------------------- |
| [`ActivationStart`](api/router/ActivationStart)           | Occurs at the start of route activation       |
| [`ChildActivationStart`](api/router/ChildActivationStart) | Occurs at the start of child route activation |
| [`ActivationEnd`](api/router/ActivationEnd)               | Occurs at the end of route activation         |
| [`ChildActivationEnd`](api/router/ChildActivationEnd)     | Occurs at the end of child route activation   |

### [Navigation completion events](#navigation-completion-events)

These events represent the final outcome of a navigation attempt. Every navigation will end with exactly one of these events, indicating whether it succeeded, was cancelled, failed, or was skipped.

| Event                                               | Description                                                         |
| --------------------------------------------------- | ------------------------------------------------------------------- |
| [`NavigationEnd`](api/router/NavigationEnd)         | Occurs when navigation ends successfully                            |
| [`NavigationCancel`](api/router/NavigationCancel)   | Occurs when the router cancels navigation                           |
| [`NavigationError`](api/router/NavigationError)     | Occurs when navigation fails due to an unexpected error             |
| [`NavigationSkipped`](api/router/NavigationSkipped) | Occurs when the router skips navigation (e.g., same URL navigation) |

### [Other events](#other-events)

There is one additional event that occurs outside the main navigation lifecycle, but it is still part of the router's event system.

| Event                         | Description             |
| ----------------------------- | ----------------------- |
| [`Scroll`](api/router/Scroll) | Occurs during scrolling |

## [Next steps](#next-steps)

Learn more about [route guards](/guide/routing/route-guards), [data fetching with resources](/guide/routing/data-fetching-with-resources), and [common router tasks](/guide/routing/common-router-tasks).

----
url: https://angular.dev/tutorials/signals/9-query-child-elements-with-signal-queries
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/8-using-signals-with-directives)[](/tutorials/signals/10-reacting-to-signal-changes-with-effect)

[](/tutorials/signals/8-using-signals-with-directives)[](/tutorials/signals/10-reacting-to-signal-changes-with-effect)

Now that you've learned [how to use signals with directives](/tutorials/signals/8-using-signals-with-directives), let's explore signal-based query APIs. These provide a reactive way to access and interact with child components and directives. Both components and directives can perform queries while also being queried themselves. Unlike the traditional ViewChild, signal queries automatically update and provide type-safe access to child components and directives.

In this activity, you'll add viewChild queries to interact with child components programmatically.

***

1. ### [Add viewChild import](#add-viewchild-import)

   First, add the [`viewChild`](/api/core/viewChild) import to access child components in `app.ts`.

   ```
   import {Component, signal, computed, viewChild, ChangeDetectionStrategy} from '@angular/core';
   ```

2. ### [Create viewChild queries](#create-viewchild-queries)

   Add viewChild queries to the App component to access child components.

   ```
   // Query APIs to access child components
   firstProduct = viewChild(ProductCard);
   cartSummary = viewChild(CartSummary);
   ```

   These queries create signals that reference child component instances.

3. ### [Implement parent methods](#implement-parent-methods)

   Use the viewChild queries to call methods on child components in `app.ts`:

   ```
   showFirstProductDetails() {
     const product = this.firstProduct();
     if (product) {
       product.highlight();
     }
   }

   initiateCheckout() {
     const summary = this.cartSummary();
     if (summary) {
       summary.initiateCheckout();
     }
   }
   ```

4. ### [Test the interactions](#test-the-interactions)

   The control buttons should now work:

   * **"Show First Product Details"** - Calls `highlight()` on the ProductCard
   * **"Initiate Checkout"** - Calls `initiateCheckout()` on the CartSummary

   Click the buttons to see how viewChild queries enable parent components to control child behavior.

Perfect! You've learned how to use signal-based query APIs for child component interaction:

In the next lesson, you'll learn about [how to react to signal changes with effect](/tutorials/signals/10-reacting-to-signal-changes-with-effect)!

----
url: https://angular.dev/update-guide
----

# Update Guide

## Select the options that match your update

### Angular versions

From v. To v.

### Application complexity

Shows information for all Angular developers.

### Other dependencies

I use ngUpgrade to combine AngularJS & Angular

I use Angular Material

I use Windows

***

----
url: https://angular.dev/api/animations/AnimationReferenceMetadata
----

```
    
      interface AnimationReferenceMetadata extends AnimationMetadata {}
    
    
```

One or more animation step objects.

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/api/forms/signals/ReadonlyArrayLike
----

```
    
      interface ReadonlyArrayLike<T> {}
    
    
```

@returns`IterableIterator<T>`

Jump to details

----
url: https://angular.dev/errors/NG0504
----

This error occurs when the `ngSkipHydration` attribute was added to an inappropriate DOM node. The `ngSkipHydration` attribute can only be applied to component host nodes either directly in the template or via a host binding. It cannot be applied to other DOM nodes and will have no effect if done so other than causing this error.

More information about hydration can be found in [this guide](guide/hydration).

The following examples will trigger the error.

## [Example 1](#example-1)

In this example, the `ngSkipHydration` attribute is applied to a `<div>` using host bindings of a directive. Since the `<div>` doesn't act as a component host node, Angular will throw an error.

```
@Directive({
  selector: '[dir]',
  host: {ngSkipHydration: 'true'},
})
class Dir {}

@Component({
  selector: 'app',
  imports: [Dir],
  template: ` <div dir></div> `,
})
class Simple {}
```

## [Example 2](#example-2)

In this example, the `ngSkipHydration` is applied to a `<div>` as an attribute via a template. Since the `<div>` doesn't act as a component host node, Angular will throw an error.

```
@Component({
  selector: 'app',
  template: ` <div ngSkipHydration></div> `,
})
class Simple {}
```

## [Debugging the error](#debugging-the-error)

Remove the `ngSkipHydration` attribute from any invalid DOM nodes. Alternatively, move the `ngSkipHydration` attribute to the component host node either in a template or via a host binding.

----
url: https://angular.dev/guide/directives/directive-composition-api
----

Angular directives offer a great way to encapsulate reusable behaviors. Directives can apply attributes, CSS classes, and event listeners to an element.

The *directive composition API* lets you apply directives to a component's host element from *within* the component TypeScript class.

## [Adding directives to a component](#adding-directives-to-a-component)

You apply directives to a component by adding a `hostDirectives` property to a component's decorator. Such directives are called *host directives*.

In this example, the `MenuBehavior` directive is applied to the host element of `AdminMenu`. This works similarly to applying the `MenuBehavior` to the `<admin-menu>` element in a template.

```
@Component({
  selector: 'admin-menu',
  templateUrl: './admin-menu.html',
  hostDirectives: [MenuBehavior],
})
export class AdminMenu {}
```

When the framework renders a component, Angular also creates an instance of each host directive. The directives' host bindings apply to the component's host element. By default, host directive inputs and outputs are not exposed as part of the component's public API. See [Including inputs and outputs](#including-inputs-and-outputs) below for more information.

Host directives come with the following constraints:

* **Angular applies host directives statically at compile time.** You cannot dynamically add directives at runtime.
* **Directives used in `hostDirectives` may not specify `standalone: false`.**
* **Angular ignores the `selector` of directives applied in the `hostDirectives` property.**

## [Including inputs and outputs](#including-inputs-and-outputs)

When you apply `hostDirectives` to your component, the inputs and outputs from the host directives are not included in your component's API by default. You can explicitly include inputs and outputs in your component's API by expanding the entry in `hostDirectives`:

```
@Component({
  selector: 'admin-menu',
  templateUrl: './admin-menu.html',
  hostDirectives: [
    {
      directive: MenuBehavior,
      inputs: ['menuId'],
      outputs: ['menuClosed'],
    },
  ],
})
export class AdminMenu {}
```

By explicitly specifying the inputs and outputs, consumers of the component with `hostDirectives` can bind them in a template:

```
<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()"></admin-menu>
```

Furthermore, you can alias inputs and outputs from a host directive to customize the API of your component:

```
@Component({
  selector: 'admin-menu',
  templateUrl: './admin-menu.html',
  hostDirectives: [
    {
      directive: MenuBehavior,
      inputs: ['menuId: id'],
      outputs: ['menuClosed: closed'],
    },
  ],
})
export class AdminMenu {}
```

```
<admin-menu id="top-menu" (closed)="logMenuClosed()"></admin-menu>
```

## [Adding directives to another directive](#adding-directives-to-another-directive)

You can also add `hostDirectives` to other directives, in addition to components. This enables the transitive aggregation of multiple behaviors.

The following example defines two directives, [`Menu`](/api/aria/menu/Menu) and `Tooltip`, then composes their behavior in `MenuWithTooltip`. Finally, it applies `MenuWithTooltip` to `SpecializedMenuWithTooltip`.

When `SpecializedMenuWithTooltip` is used in a template, it creates instances of all of [`Menu`](/api/aria/menu/Menu), `Tooltip`, and `MenuWithTooltip`. Each of these directives' host bindings apply to the host element of `SpecializedMenuWithTooltip`.

```
@Directive({/* ... */})
export class Menu {}

@Directive({/* ... */})
export class Tooltip {}

// MenuWithTooltip can compose behaviors from multiple other directives
@Directive({
  hostDirectives: [Tooltip, Menu],
})
export class MenuWithTooltip {}

// CustomWidget can apply the already-composed behaviors from MenuWithTooltip
@Directive({
  hostDirectives: [MenuWithTooltip],
})
export class SpecializedMenuWithTooltip {}
```

## [Host directive semantics](#host-directive-semantics)

### [Directive execution order](#directive-execution-order)

Host directives go through the same lifecycle as components and directives used directly in a template. However, host directives always execute their constructor, lifecycle hooks, and bindings *before* the component or directive on which they are applied.

The following example shows minimal use of a host directive:

```
@Component({
  selector: 'admin-menu',
  templateUrl: './admin-menu.html',
  hostDirectives: [MenuBehavior],
})
export class AdminMenu {}
```

The order of execution here is:

1. `MenuBehavior` instantiated
2. `AdminMenu` instantiated
3. `MenuBehavior` receives inputs (`ngOnInit`)
4. `AdminMenu` receives inputs (`ngOnInit`)
5. `MenuBehavior` applies host bindings
6. `AdminMenu` applies host bindings

This order of operations means that components with `hostDirectives` can override any host bindings specified by a host directive.

This order of operations extends to nested chains of host directives, as shown in the following example.

```
@Directive({...})
export class Tooltip { }

@Directive({
  hostDirectives: [Tooltip],
})
export class CustomTooltip { }

@Directive({
  hostDirectives: [CustomTooltip],
})
export class EvenMoreCustomTooltip { }
```

In the example above, the order of execution is:

1. `Tooltip` instantiated
2. `CustomTooltip` instantiated
3. `EvenMoreCustomTooltip` instantiated
4. `Tooltip` receives inputs (`ngOnInit`)
5. `CustomTooltip` receives inputs (`ngOnInit`)
6. `EvenMoreCustomTooltip` receives inputs (`ngOnInit`)
7. `Tooltip` applies host bindings
8. `CustomTooltip` applies host bindings
9. `EvenMoreCustomTooltip` applies host bindings

### [Dependency injection](#dependency-injection)

A component or directive that specifies `hostDirectives` can inject the instances of those host directives and vice versa.

When applying host directives to a component, both the component and host directives can define providers.

If a component or directive with `hostDirectives` and those host directives both provide the same injection token, the providers defined by class with `hostDirectives` take precedence over providers defined by the host directives.

### [Host directive de-duplication](#host-directive-de-duplication)

When the same directive appears more than once in the resolved host directive tree, it is automatically de-duplicated rather than throwing an error. Two deterministic rules are used to decide which match survives.

#### [Template match takes precedence](#template-match-takes-precedence)

If a directive matches an element once through a **template selector** and also appears as a **host directive**, Angular keeps only the template match and discards all host directive matches.

The mental model is that a host directive match represents `Partial<YourDirective>`, a partial application where only the inputs and outputs explicitly listed in `hostDirectives` are exposed, while a template match represents the full directive with its complete public API.

@Directive({selector: '\[hoverable]'}) export class Hoverable {}

```
@Component({
  selector: 'app-button',
  hostDirectives: [Hoverable],
})
export class Button {}
```

```
<!-- Hoverable is matched by selector AND as a host directive of Button. -->
<!-- Angular keeps only the selector match, which has the full public API. -->
<app-button hoverable></app-button>
```

#### [Multiple host directive matches are merged](#multiple-host-directive-matches-are-merged)

If the same directive appears **more than once as a host directive**, for example, when two directives both declare a common dependency in their `hostDirectives`, Angular merges all instances into a single directive instance. The input and output mappings from all instances are combined.

This resolves the classic [diamond problem](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem) in host directive composition:

```
// A shared behavior that both triggers need
@Directive({
  host: {
    '[attr.data-trigger-id]': 'triggerId()',
  },
})
export class TriggerRef {
  readonly triggerId = input(`trigger-${crypto.randomUUID()}`);
}

// Two separate triggers, each declaring TriggerRef as a host directive
@Directive({
  selector: '[popoverTrigger]',
  hostDirectives: [TriggerRef],
})
export class PopoverTrigger {
  readonly triggerRef = inject(TriggerRef);
}

@Directive({
  selector: '[dropdownTrigger]',
  hostDirectives: [TriggerRef],
})
export class DropdownTrigger {
  readonly triggerRef = inject(TriggerRef);
}
```

```
<!-- Angular keeps one TriggerRef instance, shared by both triggers. -->
<button popoverTrigger dropdownTrigger>Actions</button>
```

**HELPFUL:** Because Angular produces only one instance of the shared directive, both `PopoverTrigger` and `DropdownTrigger` receive the same `TriggerRef` instance when they inject it.

#### [Conflicting aliases](#conflicting-aliases)

When Angular merges duplicate host directive matches it also merges their input and output mappings.

If two instances of the same host directive expose the **same input or output under different aliases**, Angular throws an error at compile time ([NG8024](errors/NG8024)).

```
@Directive({
  selector: '[popoverTrigger]',
  hostDirectives: [{directive: TriggerRef, inputs: ['triggerId: popoverTriggerId']}],
})
export class PopoverTrigger {}

@Directive({
  selector: '[dropdownTrigger]',
  hostDirectives: [
    {directive: TriggerRef, inputs: ['triggerId: dropdownTriggerId']}, // different alias!
  ],
})
export class DropdownTrigger {}
```

```
<!-- Error: triggerId is exposed as both "popoverTriggerId" and "dropdownTriggerId". -->
<button popoverTrigger dropdownTrigger></button>
```

To resolve this, ensure that both paths expose the shared input or output under the same alias, or do not expose it at all.

## [What's next](#whats-next)

----
url: https://angular.dev/guide/testing
----

Testing your Angular application helps you check that it is working as you expect. Unit tests are crucial for catching bugs early, ensuring code quality, and facilitating safe refactoring.

**NOTE:** This guide covers the default testing setup for new Angular CLI projects, which uses Vitest. If you are migrating an existing project from Karma, see the [Migrating from Karma to Vitest guide](guide/testing/migrating-to-vitest). Karma is still supported; for more information, see the [Karma testing guide](guide/testing/karma).

## [Set up for testing](/guide/testing#set-up-for-testing)

The Angular CLI downloads and installs everything you need to test an Angular application with the [Vitest testing framework](https://vitest.dev). New projects include `vitest` and `jsdom` by default.

Vitest runs your unit tests in a Node.js environment. To simulate the browser's DOM, Vitest uses a library called `jsdom`. This allows for faster test execution by avoiding the overhead of launching a browser. You can swap `jsdom` for an alternative like `happy-dom` by installing it and uninstalling `jsdom`. Currently, `jsdom` and `happy-dom` are the supported DOM emulation libraries.

The project you create with the CLI is immediately ready to test. Run the [`ng test`](cli/test) command:

```
ng test
```

The `ng test` command builds the application in *watch mode* and launches the [Vitest test runner](https://vitest.dev).

The console output looks like this:

```
✓ src/app/app.spec.ts (3)
  ✓ AppComponent should create the app
  ✓ AppComponent should have as title 'my-app'
  ✓ AppComponent should render title
Test Files  1 passed (1)
     Tests  3 passed (3)
  Start at  18:18:01
  Duration  2.46s (transform 615ms, setup 2ms, collect 2.21s, tests 5ms)
```

The `ng test` command also watches your files for changes. If you modify a file and save it, the tests will run again.

## [Configuration](/guide/testing#configuration)

The Angular CLI handles most of the Vitest configuration for you. You can customize the test behavior by modifying the `test` target options in your `angular.json` file.

### [Angular.json options](/guide/testing#angularjson-options)

* `include`: Glob patterns for files to include for testing. Defaults to `['**/*.spec.ts', '**/*.test.ts']`.
* `exclude`: Glob patterns for files to exclude from testing.
* `setupFiles`: A list of paths to global setup files (e.g., polyfills or global mocks) that are executed before your tests.
* `providersFile`: The path to a file that exports a default array of Angular providers for the test environment. This is useful for setting up global test providers which are injected into your tests.
* `coverage`: A boolean to enable or disable code coverage reporting. Defaults to `false`.
* `browsers`: An array of browser names to run tests in a real browser (e.g., `["chromium"]`). Requires a browser provider to be installed. See the [Running tests in a browser](/guide/testing#running-tests-in-a-browser) section for more details.

### [Global test setup and providers](/guide/testing#global-test-setup-and-providers)

The `setupFiles` and `providersFile` options are particularly useful for managing global test configuration.

For example, you could create a `src/test-providers.ts` file to provide [`provideHttpClientTesting`](/api/common/http/testing/provideHttpClientTesting) to all your tests:

### src/test-providers.ts

```
import {EnvironmentProviders, Provider} from '@angular/core';
import {provideHttpClientTesting} from '@angular/common/http/testing';

const testProviders: (Provider | EnvironmentProviders)[] = [provideHttpClientTesting()];

export default testProviders;
```

You would then reference this file in your `angular.json`:

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "providersFile": "src/test-providers.ts"
          }
        }
      }
    }
  }
}
```

**HELPFUL:** When creating new TypeScript files for test setup or providers, like `src/test-providers.ts`, ensure they are included in your project's test TypeScript configuration file (typically `tsconfig.spec.json`). This allows the TypeScript compiler to properly process these files during testing.

### [Advanced Vitest configuration](/guide/testing#advanced-vitest-configuration)

For advanced use cases, you can provide a custom Vitest configuration file using the `runnerConfig` option in `angular.json`.

**IMPORTANT:** While using a custom configuration enables advanced options, the Angular team does not provide support for the contents of the configuration file or for any third-party plugins. The CLI will also override certain properties (`test.projects`, `test.include`) to ensure proper integration.

You can create a Vitest configuration file (e.g., `vitest-base.config.ts`) and reference it in your `angular.json`:

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "runnerConfig": "vitest-base.config.ts"
          }
        }
      }
    }
  }
}
```

You can also generate a base configuration file using the CLI:

```
ng generate config vitest
```

This creates a `vitest-base.config.ts` file that you can customize.

**HELPFUL:** Read more about Vitest configuration in the [official Vitest documentation](https://vitest.dev/config/).

## [Code coverage](/guide/testing#code-coverage)

You can generate a code coverage report by adding the `--coverage` flag to the `ng test` command. The report is generated in the `coverage/` directory.

For more detailed information, see the [Code coverage guide](guide/testing/code-coverage).

## [Running tests in a browser](/guide/testing#running-tests-in-a-browser)

While the default Node.js environment is faster for most unit tests, you can also run your tests in a real browser. This is useful for tests that rely on browser-specific APIs (like rendering) or for debugging.

To run tests in a browser, you must first install a browser provider. Read more about Vitest's browser mode in the [official documentation](https://vitest.dev/guide/browser).

Once the provider is installed, you can run your tests in the browser by configuring the `browsers` option in `angular.json` or by using the `--browsers` CLI flag. Tests run in a headed browser by default. If the `CI` environment variable is set, headless mode is used instead. To explicitly control headless mode, you can suffix the browser name with `Headless` (e.g., `chromiumHeadless`).

```
# Example for Playwright (headed)
ng test --browsers=chromium

# Example for Playwright (headless)
ng test --browsers=chromiumHeadless

# Example for WebdriverIO (headed)
ng test --browsers=chrome

# Example for WebdriverIO (headless)
ng test --browsers=chromeHeadless
```

Choose one of the following browser providers based on your needs:

### [Playwright](/guide/testing#playwright)

[Playwright](https://playwright.dev/) is a browser automation library that supports Chromium, Firefox, and WebKit.

### npm

```
npm install --save-dev @vitest/browser-playwright playwright
```

### [WebdriverIO](/guide/testing#webdriverio)

[WebdriverIO](https://webdriver.io/) is a browser and mobile automation test framework that supports Chrome, Firefox, Safari, and Edge.

### npm

```
npm install --save-dev @vitest/browser-webdriverio webdriverio
```

### [Preview](/guide/testing#preview)

The `@vitest/browser-preview` provider is designed for WebContainer environments like StackBlitz and is not intended for use in CI/CD.

### npm

```
npm install --save-dev @vitest/browser-preview
```

**HELPFUL:** For more advanced browser-specific configuration, see the [Advanced Vitest configuration](/guide/testing#advanced-vitest-configuration) section.

## [Other test frameworks](/guide/testing#other-test-frameworks)

You can also unit test an Angular application with other testing libraries and test runners. Each library and runner has its own installation procedures, configuration, and syntax.

## [Testing in continuous integration](/guide/testing#testing-in-continuous-integration)

A robust test suite is a key part of a continuous integration (CI) pipeline. CI servers let you automate your tests to run on every commit and pull request.

To test your Angular application in a CI server, run the standard test command:

```
ng test
```

Most CI servers set a `CI=true` environment variable, which `ng test` detects. This automatically configures your tests to run in a non-interactive, single-run mode.

If your CI server does not set this variable, or if you need to force single-run mode manually, you can use the `--no-watch` and `--no-progress` flags:

```
ng test --no-watch --no-progress
```

## [More information on testing](/guide/testing#more-information-on-testing)

After you've set up your application for testing, you might find the following testing guides useful.

|                                                                    | Details                                                                           |
| :----------------------------------------------------------------- | :-------------------------------------------------------------------------------- |
| [Code coverage](guide/testing/code-coverage)                       | How much of your app your tests are covering and how to specify required amounts. |
| [Testing services](guide/testing/services)                         | How to test the services your application uses.                                   |
| [Basics of testing components](guide/testing/components-basics)    | Basics of testing Angular components.                                             |
| [Component testing scenarios](guide/testing/components-scenarios)  | Various kinds of component testing scenarios and use cases.                       |
| [Testing attribute directives](guide/testing/attribute-directives) | How to test your attribute directives.                                            |
| [Testing pipes](guide/testing/pipes)                               | How to test pipes.                                                                |
| [Debugging tests](guide/testing/debugging)                         | Common testing bugs.                                                              |
| [Testing utility APIs](guide/testing/utility-apis)                 | Angular testing features.                                                         |

Copy link to example

----
url: https://angular.dev/tutorials/learn-angular/16-form-control-values
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/15-forms)[](/tutorials/learn-angular/17-reactive-forms)

[](/tutorials/learn-angular/15-forms)[](/tutorials/learn-angular/17-reactive-forms)

Now that your forms are set up with Angular, the next step is to access the values from the form controls.

**NOTE:** Learn more about [adding a basic form control in the in-depth guide](/guide/forms/reactive-forms#adding-a-basic-form-control).

In this activity, you'll learn how to get the value from your form input.

***

1. ### [Show the value of the input field in the template](#show-the-value-of-the-input-field-in-the-template)

   To display the input value in a template, you can use the interpolation syntax `{{}}` just like any other class property of the component:

   ```
   @Component({
     selector: 'app-user',
     template: `
       ...
       <p>Framework: {{ favoriteFramework }}</p>
       <label for="framework">
         Favorite Framework:
         <input id="framework" type="text" [(ngModel)]="favoriteFramework" />
       </label>
     `,
   })
   export class User {
     favoriteFramework = '';
   }
   ```

2. ### [Retrieve the value of an input field](#retrieve-the-value-of-an-input-field)

   When you need to reference the input field value in the component class, you can do so by accessing the class property with the `this` syntax.

   ```
   ...
   @Component({
     selector: 'app-user',
     template: `
       ...
       <button (click)="showFramework()">Show Framework</button>
     `,
     ...
   })
   export class User {
     favoriteFramework = '';
     ...

     showFramework() {
       alert(this.favoriteFramework);
     }
   }
   ```

Great job learning how to display the input values in your template and access them programmatically.

Time to progress onto the next way of managing forms with Angular: reactive forms. If you'd like to learn more about template-driven forms, please refer to the [Angular forms documentation](guide/forms/template-driven-forms).

----
url: https://angular.dev/api/platform-browser/SafeValue
----

## [API](/api/platform-browser/SafeValue#api)

```
    
      interface SafeValue {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/AutoCleanupInjectorsFeature
----

```
    
      type AutoCleanupInjectorsFeature = RouterFeature<RouterFeatureKind.AutoCleanupInjectorsFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/core/WebMcpToolDescriptor
----

```
    
      interface WebMcpToolDescriptor<InputSchema extends JsonSchemaForInference> {}
    
    
```

The unique name of this tool.

A description of what the tool does and how the agent should consider using it.

A schema which describes the input arguments expected by the [`execute`](/api/core/WebMcpToolDescriptor#execute) function which the agent must provide.

The callback function which implements this tool.

Optional annotations describing the tool's behavior and safety properties.

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8117
----

This diagnostic detects uninvoked functions in text interpolation.

```
import {Component} from '@angular/core';

@Component({
  template: `{{ getValue }}`,
})
class MyComponent {
  getValue() {
    return 'value';
  }
}
```

## [What's wrong with that?](#whats-wrong-with-that)

Functions in text interpolation should be invoked to return a value. If the function is not invoked, it will not return any value and the interpolation will not work as expected.

## [What should I do instead?](#what-should-i-do-instead)

Ensure to invoke the function when you use it in text interpolation to return the value.

```
import {Component} from '@angular/core';

@Component({
  template: `{{ getValue() }}`,
})
class MyComponent {
  getValue() {
    return 'value';
  }
}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](/tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `uninvokedFunctionInTextInterpolation` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "uninvokedFunctionInTextInterpolation": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](/extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/guide/di/lazy-loading-services
----

**IMPORTANT:** For lazy loading to work, the service you load must be auto-provided. Decorate it with either `@Injectable({providedIn: 'root'})` or [`@Service()`](guide/di/creating-and-using-services#using-the-service-vs-injectable-decorator). Without auto-provisioning, Angular has no way to construct the service after it loads.

Angular's [`injectAsync`](/api/core/injectAsync) function lets you load a service on demand, only when it's actually needed. This is useful when a service depends on a large library or rarely used feature, and you don't want to pay for it on the initial page load.

When you use [`injectAsync`](/api/core/injectAsync), the service's code is split out by your bundler into a separate JavaScript chunk and downloaded the first time you ask for the instance. Once loaded, Angular resolves the service through the regular DI system, so it can still depend on other injectables and behaves like any other singleton.

## [Lazily injecting a service](#lazily-injecting-a-service)

Imagine a `ReportExporter` that depends on a heavy spreadsheet library. Most users open the report; only a few click **Export**. Load the exporter on demand:

```
import {Component, injectAsync} from '@angular/core';

@Component({
  selector: 'app-report',
  template: `<button (click)="export()">Export</button>`,
})
export class Report {
  private exporter = injectAsync(() => import('./report-exporter').then((m) => m.ReportExporter));

  async export() {
    const exporter = await this.exporter();
    exporter.export();
  }
}
```

The first call to `this.exporter()` triggers the dynamic import and resolves the service from DI. Subsequent calls reuse the same promise, so the chunk is only fetched once.

If the lazy-loaded service is the [default export](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/export#using_the_default_export), pass the dynamic import directly, Angular unwraps the `default` for you:

### report-exporter.ts

```
@Service()
export default class ReportExporter {
  /* … */
}
```

### report.ts

```
private exporter = injectAsync(() => import('./report-exporter'));
```

## [Prefetching the dependency](#prefetching-the-dependency)

By default, the lazy chunk is only fetched when you invoke the returned function. You can start the download earlier by passing a `prefetch` trigger in the options. A trigger is any function that returns a `Promise`, when it resolves, Angular kicks off the loader.

Angular ships with [`onIdle`](/api/core/onIdle), a built-in trigger that waits until the browser becomes idle:

```
import {Component, injectAsync, onIdle} from '@angular/core';

@Component({/* … */})
export class Report {
  private exporter = injectAsync(() => import('./report-exporter').then((m) => m.ReportExporter), {
    prefetch: onIdle,
  });
}
```

You can also configure [`onIdle`](/api/core/onIdle) with a maximum wait time so the prefetch always happens within a known window, even on busy pages:

```
injectAsync(loader, {prefetch: () => onIdle({timeout: 1_000})});
```

**NOTE:** Prefetching is opportunistic. If the user invokes the feature before the prefetch fires, Angular still loads the dependency immediately and resolves your `await` as soon as it's ready.

## [Provide a custom prefetch trigger](#provide-a-custom-prefetch-trigger)

A [`PrefetchTrigger`](/api/core/PrefetchTrigger) is just a function that returns a promise, the loader runs as soon as the promise resolves. Use this to align prefetching with your own signals, such as a hover or a scheduler tick:

```
import {PrefetchTrigger} from '@angular/core';

export function onHover(target: HTMLElement): PrefetchTrigger {
  return () =>
    new Promise<void>((resolve) => {
      target.addEventListener('pointerenter', () => resolve(), {once: true});
    });
}
```

----
url: https://angular.dev/api/aria/grid/GridRow
----

```
    
      class GridRow implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

The index of this row within the grid.

@returns`void`

@returns`void`

## [Description](#description)

Represents a row within a grid. It is a container for `ngGridCell` directives.

```
<tr ngGridRow>
  <!-- ... cells ... -->
</tr>
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpHeaderResponse
----

```
    
      class HttpHeaderResponse extends HttpResponseBase {}
    
    
```

Create a new [`HttpHeaderResponse`](/api/common/http/HttpHeaderResponse) with the given parameters.

@paraminit`{ headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; }`

@returns`HttpHeaderResponse`

Copy this [`HttpHeaderResponse`](/api/common/http/HttpHeaderResponse), overriding its contents with the given parameter hash.

@paramupdate`{ headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; }`

@returns`HttpHeaderResponse`

A partial HTTP response which only includes the status and header data, but no response body.

[`HttpHeaderResponse`](/api/common/http/HttpHeaderResponse) is a [`HttpEvent`](/api/common/http/HttpEvent) available on the response event stream, only when progress events are requested.

Jump to details

----
url: https://angular.dev/api/core/testing/tick
----

```
function tick(
  millis?: number,
  tickOptions?: { processNewMacroTasksSynchronously: boolean },
): void;
```

@parammillis`number`

The number of milliseconds to advance the virtual timer.

@paramtickOptions`{ processNewMacroTasksSynchronously: boolean; }`

The options to pass to the [`tick()`](/api/core/testing/tick) function.

@returns`void`

## [Description](#description)

**IMPORTANT:** This API requires Zone.js and cannot be used with the Vitest test runner

Simulates the asynchronous passage of time for the timers in the [`fakeAsync`](/api/core/testing/fakeAsync) zone.

The microtasks queue is drained at the very start of this function and after any timer callback has been executed.

## [Usage Notes](#usage-notes)

The [`tick()`](/api/core/testing/tick) option is a flag called `processNewMacroTasksSynchronously`, which determines whether or not to invoke new macroTasks.

If you provide a `tickOptions` object, but do not specify a `processNewMacroTasksSynchronously` property (`tick(100, {})`), then `processNewMacroTasksSynchronously` defaults to true.

If you omit the `tickOptions` parameter (`tick(100))`), then `tickOptions` defaults to `{processNewMacroTasksSynchronously: true}`.

### [Example](#example)

```
describe('this test', () => {
  it(
    'looks async but is synchronous',
    <any>fakeAsync((): void => {
      let flag = false;
      setTimeout(() => {
        flag = true;
      }, 100);
      expect(flag).toBe(false);
      tick(50);
      expect(flag).toBe(false);
      tick(50);
      expect(flag).toBe(true);
    }),
  );
});
```

The following example includes a nested timeout (new macroTask), and the `tickOptions` parameter is allowed to default. In this case, `processNewMacroTasksSynchronously` defaults to true, and the nested function is executed on each tick.

```
it ('test with nested setTimeout', fakeAsync(() => {
  let nestedTimeoutInvoked = false;
  function funcWithNestedTimeout() {
    setTimeout(() => {
      nestedTimeoutInvoked = true;
    });
  };
  setTimeout(funcWithNestedTimeout);
  tick();
  expect(nestedTimeoutInvoked).toBe(true);
}));
```

In the following case, `processNewMacroTasksSynchronously` is explicitly set to false, so the nested timeout function is not invoked.

```
it ('test with nested setTimeout', fakeAsync(() => {
  let nestedTimeoutInvoked = false;
  function funcWithNestedTimeout() {
    setTimeout(() => {
      nestedTimeoutInvoked = true;
    });
  };
  setTimeout(funcWithNestedTimeout);
  tick(0, {processNewMacroTasksSynchronously: false});
  expect(nestedTimeoutInvoked).toBe(false);
}));
```

Jump to details

----
url: https://angular.dev/errors/NG8011
----

An element or `<ng-template>` inside a control flow block would be projected into a specific `<ng-content>` slot, but isn't because the block has more than one root node.

```
import {Component} from '@angular/core';

@Component({
  selector: 'app-card',
  template: `
    <ng-content select="[card-title]" />
    <ng-content />
  `,
})
class Card {}

@Component({
  imports: [Card],
  // NG8011: <h2 card-title> is not projected into the "[card-title]" slot because the @if
  // block has two root nodes (the h2 and the p).
  template: `
    <app-card>
      @if (showTitle) {
        <h2 card-title>Title</h2>
        <p>Subtitle</p>
      }
    </app-card>
  `,
})
class MyComponent {
  showTitle = true;
}
```

## [Debugging the error](#debugging-the-error)

Control flow blocks emulate the content projection behavior of `*ngIf` and `*ngFor`, which project the element they are applied to. This only works if the block has a single root node. When the block has more than one root node, Angular can't use the element to choose a slot, so `<h2 card-title>` ends up in the default `<ng-content />` slot of `Card` instead of the `[card-title]` slot.

Text counts as a root node, so a stray line of text next to the projected element causes the same problem, unless the component that contains the block sets `preserveWhitespaces: true`, in which case whitespace counts as well.

To resolve the error, pick one of the following approaches:

* **Wrap the content in an `<ng-container>` that uses [`ngProjectAs`](guide/components/content-projection#aliasing-content-for-projection)** to match the selector of the slot. Everything inside the container is projected into that slot.

  ```
  @Component({
    imports: [Card],
    template: `
      <app-card>
        @if (showTitle) {
          <ng-container ngProjectAs="[card-title]">
            <h2>Title</h2>
            <p>Subtitle</p>
          </ng-container>
        }
      </app-card>
    `,
  })
  class MyComponent {
    showTitle = true;
  }
  ```

* **Split the content across multiple blocks**, so that each one has a single projectable node at its root.

  ```
  @Component({
    imports: [Card],
    template: `
      <app-card>
        @if (showTitle) {
          <h2 card-title>Title</h2>
        }
        @if (showTitle) {
          <p>Subtitle</p>
        }
      </app-card>
    `,
  })
  class MyComponent {
    showTitle = true;
  }
  ```

----
url: https://angular.dev/api/common/formatPercent
----

```
function formatPercent(
  value: number,
  locale: string,
  digitsInfo?: string | undefined,
): string;
```

The formatted percentage value.

## [Description](#description)

Formats a number as a percentage according to locale rules.

## [Exported by](#exported-by)

* `CommonModule`

Jump to details

----
url: https://angular.dev/api/router/ComponentInputBindingOptions
----

```
    
      interface ComponentInputBindingOptions {}
    
    
```

When true (default), will configure query parameters to bind to component inputs.

Configures the behavior when an input is not matched by any key in the router data.

* `'alwaysUndefined'`: (Default) Binds `undefined` to the input. This ensures that stale data is not retained.
* `'undefinedIfStale'`: Binds `undefined` only if the input was previously available in the router data during the lifetime of the active route in this outlet. This avoids setting `undefined` for inputs that were never expected to be set by the router.

Jump to details

----
url: https://angular.dev/reference/migrations/signal-inputs
----

Angular introduced an improved API for inputs that is considered production ready as of v19. Read more about signal inputs and their benefits in the [dedicated guide](guide/components/inputs).

To support existing teams that would like to use signal inputs, the Angular team provides an automated migration that converts [`@Input`](/api/core/Input) fields to the new [`input()`](/api/core/input) API.

Run the schematic using the following command:

```
ng generate @angular/core:signal-input-migration
```

Alternatively, the migration is available as a [code refactor action](https://code.visualstudio.com/docs/typescript/typescript-refactoring#_refactoring) in VSCode. Install the latest version of the VSCode extension and click on an [`@Input`](/api/core/Input) field. See more details in the section [below](#vscode-extension).

## [What does the migration change?](#what-does-the-migration-change)

1. [`@Input()`](/api/core/Input) class members are updated to their signal [`input()`](/api/core/input) equivalent.
2. References to migrated inputs are updated to call the signal.
   * This includes references in templates, host bindings or TypeScript code.

**Before**

```
import {Component, Input} from '@angular/core';

@Component({
  template: `Name: {{ name ?? '' }}`,
})
export class MyComponent {
  @Input() name: string | undefined = undefined;

  someMethod(): number {
    if (this.name) {
      return this.name.length;
    }
    return -1;
  }
}
```

**After**

```
import {Component, input} from '@angular/core';

@Component({
  template: `Name: {{ name() ?? '' }}`,
})
export class MyComponent {
  readonly name = input<string>();

  someMethod(): number {
    const name = this.name();
    if (name) {
      return name.length;
    }
    return -1;
  }
}
```

## [Configuration options](#configuration-options)

The migration supports a few options for fine tuning the migration to your specific needs.

### [`--path`](#--path)

By default, the migration will update your whole Angular CLI workspace. You can limit the migration to a specific sub-directory using this option.

### [`--best-effort-mode`](#--best-effort-mode)

By default, the migration skips inputs that cannot be safely migrated. The migration tries to refactor code as safely as possible.

When the `--best-effort-mode` flag is enabled, the migration eagerly tries to migrate as much as possible, even if it could break your build.

### [`--insert-todos`](#--insert-todos)

When enabled, the migration will add TODOs to inputs that couldn't be migrated. The TODOs will include reasoning on why inputs were skipped. E.g.

```
// TODO: Skipped for migration because:
//  Your application code writes to the input. This prevents migration.
@Input() myInput = false;
```

### [`--analysis-dir`](#--analysis-dir)

In large projects you may use this option to reduce the amount of files being analyzed. By default, the migration analyzes the whole workspace, regardless of the `--path` option, in order to update all references affected by an [`@Input()`](/api/core/Input) migration.

With this option, you can limit analysis to a sub-folder. Note that this means that any references outside this directory are silently skipped, potentially breaking your build.

## [VSCode extension](#vscode-extension)

The migration is available as a [code refactor action](https://code.visualstudio.com/docs/typescript/typescript-refactoring#_refactoring) in VSCode.

To make use of the migration via VSCode, install the latest version of the VSCode extension and either click:

* on a [`@Input`](/api/core/Input) field.
* or, on a directive/component

Then, wait for the yellow lightbulb VSCode refactoring button to appear. Via this button you can then select the signal input migration.

----
url: https://angular.dev/api/core/Renderer2
----

```
    
      abstract class Renderer2 {}
    
    
```

Use to store arbitrary developer-defined data on a renderer instance, as an object containing key-value pairs. This is useful for renderers that delegate to other renderers.

Implement this callback to destroy the renderer or the host element.

@returns`void`

Implement this callback to create an instance of the host element.

@paramname`string`

An identifying name for the new element, unique within the namespace.

@paramnamespace`string | null | undefined`

The namespace for the new element.

@returns`any`

The new element.

Implement this callback to add a comment to the DOM of the host element.

@paramvalue`string`

The comment text.

@returns`any`

The modified element.

Implement this callback to add text to the DOM of the host element.

@paramvalue`string`

The text string.

@returns`any`

The modified element.

If null or undefined, the view engine won't call it. This is used as a performance optimization for production mode.

Appends a child to a given parent node in the host element DOM.

@paramparent`any`

The parent node.

@paramnewChild`any`

The new child node.

@returns`void`

Implement this callback to insert a child node at a given position in a parent node in the host element DOM.

@paramparent`any`

The parent node.

@paramnewChild`any`

The new child nodes.

@paramrefChild`any`

The existing child node before which `newChild` is inserted.

@paramisMove`boolean | undefined`

Optional argument which signifies if the current `insertBefore` is a result of a move. Animation uses this information to trigger move animations. In the past the Animation would always assume that any `insertBefore` is a move. This is not strictly true because with runtime i18n it is possible to invoke `insertBefore` as a result of i18n and it should not trigger an animation move.

@returns`void`

Implement this callback to remove a child node from the host element's DOM.

@paramparent`any`

The parent node.

@paramoldChild`any`

The child node to remove.

@paramisHostElement`boolean | undefined`

Optionally signal to the renderer whether this element is a host element

@paramrequireSynchronousElementRemoval`boolean | undefined`

Optionally signal to the renderer whether this element needs synchronous removal

@returns`void`

Implement this callback to prepare an element to be bootstrapped as a root element, and return the element instance.

@paramselectorOrNode`any`

The DOM element.

@parampreserveContent`boolean | undefined`

Whether the contents of the root element should be preserved, or cleared upon bootstrap (default behavior). Use with [`ViewEncapsulation.ShadowDom`](/api/core/ViewEncapsulation#ShadowDom) to allow simple native content projection via `<slot>` elements.

@returns`any`

The root element.

Implement this callback to get the parent of a given node in the host element's DOM.

@paramnode`any`

The child node to query.

@returns`any`

The parent node, or null if there is no parent. This is because the check is synchronous, and the caller can't rely on checking for null.

Implement this callback to get the next sibling node of a given node in the host element's DOM.

@paramnode`any`

@returns`any`

The sibling node, or null if there is no sibling. This is because the check is synchronous, and the caller can't rely on checking for null.

Implement this callback to set an attribute value for an element in the DOM.

@paramel`any`

The element.

@paramname`string`

The attribute name.

@paramvalue`string`

The new value.

@paramnamespace`string | null | undefined`

The namespace.

@returns`void`

Implement this callback to remove an attribute from an element in the DOM.

@paramel`any`

The element.

@paramname`string`

The attribute name.

@paramnamespace`string | null | undefined`

The namespace.

@returns`void`

Implement this callback to add a class to an element in the DOM.

@paramel`any`

The element.

@paramname`string`

The class name.

@returns`void`

Implement this callback to remove a class from an element in the DOM.

@paramel`any`

The element.

@paramname`string`

The class name.

@returns`void`

Implement this callback to set a CSS style for an element in the DOM.

@paramel`any`

The element.

@paramstyle`string`

The name of the style.

@paramvalue`any`

The new value.

@paramflags`RendererStyleFlags2 | undefined`

Flags for style variations. No flags are set by default.

@returns`void`

Implement this callback to remove the value from a CSS style for an element in the DOM.

@paramel`any`

The element.

@paramstyle`string`

The name of the style.

@paramflags`RendererStyleFlags2 | undefined`

Flags for style variations to remove, if set. ???

@returns`void`

Implement this callback to set the value of a property of an element in the DOM.

@paramel`any`

The element.

@paramname`string`

The property name.

@paramvalue`any`

The new value.

@returns`void`

Implement this callback to set the value of a node in the host element.

@paramnode`any`

The node.

@paramvalue`string`

The new value.

@returns`void`

Implement this callback to start an event listener.

@paramtarget`any`

The context in which to listen for events. Can be the entire window or document, the body of the document, or a specific DOM element.

@parameventName`string`

The event to listen for.

@paramcallback`(event: any) => boolean | void`

A handler function to invoke when the event occurs.

@paramoptions`ListenerOptions | undefined`

Options that configure how the event listener is bound.

@returns`() => void`

An "unlisten" function for disposing of this handler.

## [Description](#description)

Extend this base class to implement custom rendering. By default, Angular renders a template into DOM. You can use custom rendering to intercept rendering calls, or to render to something other than DOM.

Please be aware that usage of \`Renderer2\`, in context of accessing DOM elements, provides no extra security which makes it equivalent to [`Security vulnerabilities`](/best-practices/security#direct-use-of-the-dom-apis-and-explicit-sanitization-calls).

Create your custom renderer using [`RendererFactory2`](/api/core/RendererFactory2).

Use a custom renderer to bypass Angular's templating and make custom UI changes that can't be expressed declaratively. For example if you need to set a property or an attribute whose name is not statically known, use the `setProperty()` or `setAttribute()` method.

Jump to details

----
url: https://angular.dev/errors/NG0203
----

You see this error when you try to use the [`inject`](api/core/inject) function outside of the allowed [injection context](guide/di/dependency-injection-context). The injection context is available during the class creation and initialization. It is also available to functions used with [`runInInjectionContext`](/api/core/runInInjectionContext).

In practice the `inject()` calls are allowed in a constructor, a constructor parameter and a field initializer:

```
@Service()
export class Car {
  radio: Radio | undefined;

  // OK: field initializer
  spareTyre = inject(Tyre);

  constructor() {
    // OK: constructor body
    this.radio = inject(Radio);
  }
}
```

It is also legal to call [`inject`](api/core/inject) from a provider's factory:

```
providers: [
  {
    provide: Car,
    useFactory: () => {
      // OK: a class factory
      const engine = inject(Engine);
      return new Car(engine);
    },
  },
];
```

Calls to the [`inject`](api/core/inject) function outside of the class creation or [`runInInjectionContext`](/api/core/runInInjectionContext) will result in error. Most notably, calls to `inject()` are disallowed after a class instance was created, in methods (including lifecycle hooks):

```
@Component({ ... })
export class Car {
  ngOnInit() {
    // ERROR: too late, the component instance was already created
    const engine = inject(Engine);
    engine.start();
  }
}
```

## [Debugging the error](#debugging-the-error)

Work backwards from the stack trace of the error to identify a place where the disallowed call to `inject()` is located.

To fix the error move the [`inject`](api/core/inject) call to an allowed place (usually a class constructor or a field initializer).

**NOTE:** If you are running in a test context, [`TestBed.runInInjectionContext`](/api/core/testing/TestBed#runInInjectionContext) will enable `inject()` to succeed.

```
TestBed.runInInjectionContext(() => {
  // ...
});
```

----
url: https://angular.dev/guide/forms/signals/testing
----

Forms are often critical to applications, and testing gives you confidence they behave correctly as the codebase changes. Signal Forms keeps most of its logic in the schema rather than the template, which means you can test the majority of form behavior without rendering a component.

This guide walks through how to set up those tests, starting with isolated logic tests and then covering component-bound tests for cases where DOM interaction matters.

## [Testing form logic in isolation](#testing-form-logic-in-isolation)

When you only need to verify validation, disabled state, required state, or error output, test the form directly instead of rendering a component. Isolated tests keep the setup small and let the test focus on the form's behavior.

The key requirement is the injector. Signal Forms needs an injection context during form creation. If a test calls [`form()`](/api/forms/signals/form) without one, the call throws before the test can assert anything about the form.

The most direct way to satisfy this requirement is to pass an injector explicitly. The following test creates a form with a `required` rule and verifies that the field becomes valid after receiving a value:

### profile-form.spec.ts

```
import {Injector, signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {form, required} from '@angular/forms/signals';
import {describe, expect, it} from 'vitest';

describe('profile form', () => {
  it('marks required fields as invalid until they have a value', () => {
    const model = signal({name: ''});

    const profileForm = form(
      model,
      (path) => {
        required(path.name);
      },
      {injector: TestBed.inject(Injector)},
    );

    expect(profileForm.name().valid()).toBe(false);
    expect(profileForm.name().errors()).toEqual([expect.objectContaining({kind: 'required'})]);

    profileForm.name().value.set('Ada');

    expect(profileForm.name().valid()).toBe(true);
    expect(profileForm.name().errors()).toEqual([]);
  });
});
```

This pattern works well for most isolated tests because the injector requirement stays visible at the call site. It also mirrors how Signal Forms unit tests in Angular's source create forms.

When the code under test calls [`form()`](/api/forms/signals/form) internally, you may not be able to pass the injector directly. In that case, wrap the call in an ambient injection context:

### profile-form.spec.ts

```
import {signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {form, required} from '@angular/forms/signals';
import {describe, expect, it} from 'vitest';

describe('profile form', () => {
  it('can create a form inside an injection context', () => {
    const model = signal({name: ''});

    TestBed.runInInjectionContext(() => {
      const profileForm = form(model, (path) => {
        required(path.name);
      });

      expect(profileForm.name().valid()).toBe(false);
    });
  });
});
```

Both patterns produce the same kind of form. Passing `{injector}` is often the clearest choice when the test creates the form directly. [`TestBed.runInInjectionContext()`](/api/core/testing/TestBed#runInInjectionContext) is useful when the code under test calls [`form()`](/api/forms/signals/form) internally and you need to supply the surrounding injection context.

Once the form exists, test it through field state signals. Common assertions include `valid()`, `invalid()`, [`disabled()`](/api/forms/signals/disabled), `required()`, and `errors()`. For most form logic, that is enough to verify the behavior without involving the DOM.

## [Testing a form with multiple rules](#testing-a-form-with-multiple-rules)

After the injector setup is in place, a good next step is a complete test that exercises a few pieces of form logic together. This kind of test is still isolated, but it looks much closer to a real application form.

For example, this test verifies both a basic required rule and a conditional required rule that depends on another field:

### profile-form.spec.ts

```
import {Injector, signal} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {form, required} from '@angular/forms/signals';
import {describe, expect, it} from 'vitest';

describe('profile form', () => {
  it('updates validation state when related fields change', () => {
    const model = signal({
      name: '',
      age: 5,
    });

    const profileForm = form(
      model,
      (path) => {
        required(path.name);
        required(path.name, {
          error: (ctx) => ({kind: `required-${ctx.valueOf(path.age)}`}),
          when: ({valueOf}) => valueOf(path.age) > 10,
        });
      },
      {injector: TestBed.inject(Injector)},
    );

    expect(profileForm.name().invalid()).toBe(true);
    expect(profileForm.name().errors()).toEqual([expect.objectContaining({kind: 'required'})]);

    profileForm.age().value.set(15);

    expect(profileForm.name().errors()).toEqual([
      expect.objectContaining({kind: 'required'}),
      expect.objectContaining({kind: 'required-15'}),
    ]);

    profileForm.name().value.set('Ada');

    expect(profileForm.name().valid()).toBe(true);
    expect(profileForm.name().errors()).toEqual([]);
  });
});
```

This example shows an important testing pattern: update one field, then assert against the state of another field. Because Signal Forms rules are reactive, a field's validation can depend on sibling values, parent values, or other derived conditions. Tests should verify those relationships directly instead of only checking the field that changed.

For validation-focused tests, `errors()` is usually the most useful assertion. `valid()` and `invalid()` tell you whether the field currently passes validation, but `errors()` shows which rule produced the failure. That becomes especially useful once a field has multiple validators or conditional rules.

The same structure works for most everyday form tests:

1. Create a model signal with the smallest shape that reproduces the behavior.
2. Build the form with an explicit injector.
3. Assert the initial field state.
4. Change a field with `.value.set(...)`, including sibling fields when testing cross-field rules.
5. Assert the updated state signals, usually `errors()`, `valid()`, or `invalid()`.

When a test is about schema behavior rather than rendering, default to this isolated style. It is faster than a component test and makes it easier to see which rule is responsible when the behavior changes.

## [Testing forms bound to components](#testing-forms-bound-to-components)

When you need to verify behavior that depends on template bindings, user interaction through `dispatchEvent`, or custom form controls that manage their own rendering, isolated tests are not enough. You need component-bound tests to render the template so that you can interact with actual DOM elements.

### [Setting up a component test](#setting-up-a-component-test)

Component-bound tests render the component so you can interact with actual DOM elements. Create the component with [`TestBed.createComponent()`](/api/core/testing/TestBed#createComponent) and wait for rendering to complete before asserting:

### profile-form.ts

```
import {Component, signal} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';

@Component({
  selector: 'app-profile-form',
  imports: [FormField],
  template: `<input [formField]="profileForm.name" />`,
})
export class ProfileForm {
  readonly model = signal({name: 'Ada'});
  readonly profileForm = form(this.model, (path) => {
    required(path.name);
  });
}
```

### profile-form.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {describe, expect, it} from 'vitest';
import {ProfileForm} from './profile-form';

describe('ProfileForm', () => {
  it('reflects model values in the DOM and updates the model on user input', async () => {
    const fixture = TestBed.createComponent(ProfileForm);
    await fixture.whenStable();

    const input = fixture.nativeElement.querySelector('input') as HTMLInputElement;

    // Model → View: the input reflects the model's initial value
    expect(input.value).toBe('Ada');

    // View → Model: simulate the user clearing the field
    input.value = '';
    input.dispatchEvent(new Event('input'));
    await fixture.whenStable();

    expect(fixture.componentInstance.profileForm.name().value()).toBe('');
    expect(fixture.componentInstance.profileForm.name().valid()).toBe(false);
  });
});
```

Notice that the component uses [`form()`](/api/forms/signals/form) without an explicit injector because the component's own injection context provides it automatically. After each change, `await fixture.whenStable()` waits for rendering and effects to complete before asserting.

The same pattern works for async operations such as async validators or server calls. Call `await fixture.whenStable()` after the async work resolves.

## [When to use each approach](#when-to-use-each-approach)

| What you need to verify                                                                                                  | Approach        |
| ------------------------------------------------------------------------------------------------------------------------ | --------------- |
| Validation rules, `errors()`, `valid()`, `invalid()`                                                                     | Isolated        |
| Disabled, required, or readonly state                                                                                    | Isolated        |
| Cross-field reactive dependencies                                                                                        | Isolated        |
| Conditional schemas ([`applyWhen`](/api/forms/signals/applyWhen), [`applyWhenValue`](/api/forms/signals/applyWhenValue)) | Isolated        |
| Input values rendering in the DOM                                                                                        | Component-bound |
| User typing updating the model                                                                                           | Component-bound |
| Custom form controls with their own templates                                                                            | Component-bound |
| Focus management or accessibility attributes                                                                             | Component-bound |

Most forms only need isolated tests. The form's logic (such as validation, disabled state, cross-field rules) lives in the schema, and schemas do not need a template to run. Component-bound tests add value when the behavior you care about crosses the boundary between the form and the DOM.

## [Next steps](#next-steps)

This guide covered testing Signal Forms in isolation and with component templates. Here are related guides that explore other aspects of Signal Forms:

----
url: https://angular.dev/api/aria/tabs/Tabs
----

```
    
      class Tabs implements OnDestroy {}
    
    
```

A reference to the host element.

@returns`void`

## [Description](#description)

A Tabs container.

The `ngTabs` directive represents a set of layered sections of content. It acts as the overarching container for a tabbed interface, coordinating the behavior of `ngTabList`, `ngTab`, and `ngTabPanel` directives.

```
<div ngTabs>
  <ul ngTabList [(selectedTab)]="selectedTabValue">
    <li ngTab value="tab1">Tab 1</li>
    <li ngTab value="tab2">Tab 2</li>
    <li ngTab value="tab3">Tab 3</li>
  </ul>

  <div ngTabPanel value="tab1">
     <ng-template ngTabContent>Content for Tab 1</ng-template>
  </div>
  <div ngTabPanel value="tab2">
     <ng-template ngTabContent>Content for Tab 2</ng-template>
  </div>
  <div ngTabPanel value="tab3">
     <ng-template ngTabContent>Content for Tab 3</ng-template>
  </div>
</div>
```

Jump to details

----
url: https://angular.dev/api/core/isStandalone
----

```
function isStandalone(type: Type<unknown>): boolean;
```

@paramtype`Type<unknown>`

A reference to a Component, Directive or Pipe.

@returns`boolean`

Jump to details

----
url: https://angular.dev/guide/i18n/merge
----

To merge the completed translations into your project, complete the following actions

1. Use the [Angular CLI](cli "CLI Overview and Command Reference | Angular") to build a copy of the distributable files of your project
2. Use the `"localize"` option to replace all of the i18n messages with the valid translations and build a localized variant application. A variant application is a complete copy of the distributable files of your application translated for a single locale.

After you merge the translations, serve each distributable copy of the application using server-side language detection or different subdirectories.

**HELPFUL:** For more information about how to serve each distributable copy of the application, see [deploying multiple locales](guide/i18n/deploy).

For a compile-time translation of the application, the build process uses ahead-of-time (AOT) compilation to produce a small, fast, ready-to-run application.

**HELPFUL:** For a detailed explanation of the build process, see [Building and serving Angular apps](tools/cli/build "Building and serving Angular apps | Angular"). The build process works for translation files in the `.xlf` format or in another format that Angular understands, such as `.xtb`. For more information about translation file formats used by Angular, see [Change the source language file format](guide/i18n/translation-files#change-the-source-language-file-format "Change the source language file format - Work with translation files | Angular")

To build a separate distributable copy of the application for each locale, [define the locales in the build configuration](guide/i18n/merge#define-locales-in-the-build-configuration "Define locales in the build configuration - Merge translations into the application | Angular") in the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file of your project.

This method shortens the build process by removing the requirement to perform a full application build for each locale.

To [generate application variants for each locale](guide/i18n/merge#generate-application-variants-for-each-locale "Generate application variants for each locale - Merge translations into the application | Angular"), use the `"localize"` option in the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file. Also, to [build from the command line](guide/i18n/merge#build-from-the-command-line "Build from the command line - Merge translations into the application | Angular"), use the [`build`](cli/build "ng build | CLI | Angular") [Angular CLI](cli "CLI Overview and Command Reference | Angular") command with the `--localize` option.

**HELPFUL:** Optionally, [apply specific build options for just one locale](guide/i18n/merge#apply-specific-build-options-for-just-one-locale "Apply specific build options for just one locale - Merge translations into the application | Angular") for a custom locale configuration.

## [Define locales in the build configuration](#define-locales-in-the-build-configuration)

Use the `i18n` project option in the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file of your project to define locales for a project.

The following sub-options identify the source language and tell the compiler where to find supported translations for the project.

| Suboption      | Details                                                                                                                                              |
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sourceLocale` | The locale you use within the application source code (`en-US` by default). Can also be an object with `code`, `baseHref`, and `subPath` properties. |
| `locales`      | A map of locale identifiers to translation files. Each entry can also be an object with `translation`, `baseHref`, and `subPath` properties.         |

For the full list of `i18n` properties and their types, see [i18n options](reference/configs/workspace-config#i18n-options "i18n options - Angular workspace configuration | Angular").

### [`angular.json` for `en-US` and `fr` example](#angularjson-for-en-us-and-fr-example)

For example, the following excerpt of an [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file sets the source locale to `en-US` and provides the path to the French (`fr`) locale translation file.

### angular.json

```
"projects": {
    "angular.io-example": {
      // ...
      "i18n": {
        "sourceLocale": "en-US",
        "locales": {
          "fr": {
            "translation": "src/locale/messages.fr.xlf",
            // ...
          }
        }
      },
      "architect": {
        // ...
      }
    }
  }
}
```

## [Generate application variants for each locale](#generate-application-variants-for-each-locale)

To use your locale definition in the build configuration, use the `"localize"` option in the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file to tell the CLI which locales to generate for the build configuration.

* Set `"localize"` to `true` for all the locales previously defined in the build configuration.
* Set `"localize"` to an array of a subset of the previously defined locale identifiers to build only those locale versions.
* Set `"localize"` to `false` to disable localization and not generate any locale-specific versions.

**HELPFUL:** Ahead-of-time (AOT) compilation is required to localize component templates.

If you changed this setting, set `"aot"` to `true` in order to use AOT.

**HELPFUL:** Due to the deployment complexities of i18n and the need to minimize rebuild time, the development server only supports localizing a single locale at a time. If you set the `"localize"` option to `true`, define more than one locale, and use `ng serve`; then an error occurs. If you want to develop against a specific locale, set the `"localize"` option to a specific locale. For example, for French (`fr`), specify `"localize": ["fr"]`.

The CLI loads and registers the locale data, places each generated version in a locale-specific directory to keep it separate from other locale versions, and puts the directories within the configured `outputPath` for the project. For each application variant the `lang` attribute of the `html` element is set to the locale. The CLI also adjusts the HTML base HREF for each version of the application by adding the locale to the configured `baseHref`.

Set the `"localize"` property as a shared configuration to effectively inherit for all the configurations. Also, set the property to override other configurations.

### [`angular.json` include all locales from build example](#angularjson-include-all-locales-from-build-example)

The following example displays the `"localize"` option set to `true` in the [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file, so that all locales defined in the build configuration are built.

### angular.json

```
"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    "localize": true,
    // ...
  },
```

## [Build from the command line](#build-from-the-command-line)

Also, use the `--localize` option with the [`ng build`](cli/build "ng build | CLI | Angular") command and your existing `production` configuration. The CLI builds all locales defined in the build configuration. If you set the locales in build configuration, it is similar to when you set the `"localize"` option to `true`.

**HELPFUL:** For more information about how to set the locales, see [Generate application variants for each locale](guide/i18n/merge#generate-application-variants-for-each-locale "Generate application variants for each locale - Merge translations into the application | Angular").

```
ng build --localize
```

## [Apply specific build options for just one locale](#apply-specific-build-options-for-just-one-locale)

To apply specific build options to only one locale, specify a single locale to create a custom locale-specific configuration.

**IMPORTANT:** Use the [Angular CLI](cli "CLI Overview and Command Reference | Angular") development server (`ng serve`) with only a single locale.

### [build for French example](#build-for-french-example)

The following example displays a custom locale-specific configuration using a single locale.

### angular.json

```
"build": {
    // ...
    "configurations": {
      // ...
      "fr": {
        "localize": ["fr"]
      }
    },
    // ...
  },
  "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "configurations": {
      // ...
      "fr": {
        "buildTarget": "angular.io-example:build:development,fr"
      }
    },
    // ...
  },
  // ...
}
```

Pass this configuration to the `ng serve` or `ng build` commands. The following code example displays how to serve the French language file.

```
ng serve --configuration=fr
```

For production builds, use configuration composition to run both configurations.

```
ng build --configuration=production,fr
```

### angular.json

```
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": {
      // ...
    },
    "configurations": {
      // ...
      "fr": {
        "localize": ["fr"]
      }
    },
    // ...
  },
  "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "configurations": {
      "production": {
        "buildTarget": "angular.io-example:build:production"
      },
      // ...
      "fr": {
        "buildTarget": "angular.io-example:build:development,fr"
      }
    },
    // ...
  },
  // ...
}
```

## [Report missing translations](#report-missing-translations)

When a translation is missing, the build succeeds but generates a warning such as `Missing translation for message "{translation_text}"`. To configure the level of warning that is generated by the Angular compiler, specify one of the following levels.

| Warning level | Details                                              | Output                                                 |
| :------------ | :--------------------------------------------------- | :----------------------------------------------------- |
| `error`       | Throw an error and the build fails                   | n/a                                                    |
| `ignore`      | Do nothing                                           | n/a                                                    |
| `warning`     | Displays the default warning in the console or shell | `Missing translation for message "{translation_text}"` |

Specify the warning level in the `options` section for the `build` target of your [`angular.json`](reference/configs/workspace-config "Angular workspace configuration | Angular") workspace build configuration file.

### [`angular.json` `error` warning example](#angularjson-error-warning-example)

The following example displays how to set the warning level to `error`.

### angular.json

```
"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    // ...
    "i18nMissingTranslation": "error"
  },
```

**HELPFUL:** When you compile your Angular project into an Angular application, the instances of the `i18n` attribute are replaced with instances of the [`$localize`](api/localize/init/$localize "$localize | init - localize - API | Angular") tagged message string. This means that your Angular application is translated after compilation. This also means that you can create localized versions of your Angular application without re-compiling your entire Angular project for each locale.

When you translate your Angular application, the *translation transformation* replaces and reorders the parts (static strings and expressions) of the template literal string with strings from a collection of translations. For more information, see [`$localize`](api/localize/init/$localize "$localize | init - localize - API | Angular").

**TL;DR:** Compile once, then translate for each locale.

## [What's next](#whats-next)

----
url: https://angular.dev/tools/cli/cli-builder
----

A number of Angular CLI commands run a complex process on your code, such as building, testing, or serving your application. The commands use an internal tool called Architect to run *CLI builders*, which invoke another tool (bundler, test runner, server) to accomplish the desired task. Custom builders can perform an entirely new task, or to change which third-party tool is used by an existing command.

This document explains how CLI builders integrate with the workspace configuration file, and shows how you can create your own builder.

**HELPFUL:** Find the code from the examples used here in this [GitHub repository](https://github.com/mgechev/cli-builders-demo).

## [CLI builders](#cli-builders)

The internal Architect tool delegates work to handler functions called *builders*. A builder handler function receives two arguments:

| Argument  | Type             |
| :-------- | :--------------- |
| `options` | `JSONObject`     |
| `context` | `BuilderContext` |

The separation of concerns here is the same as with [schematics](tools/cli/schematics-authoring), which are used for other CLI commands that touch your code (such as `ng generate`).

* The `options` object is provided by the CLI user's options and configuration, while the `context` object is provided by the CLI Builder API automatically.
* In addition to the contextual information, the `context` object also provides access to a scheduling method, `context.scheduleTarget()`. The scheduler executes the builder handler function with a given target configuration.

The builder handler function can be synchronous (return a value), asynchronous (return a `Promise`), or watch and return multiple values (return an `Observable`). The return values must always be of type `BuilderOutput`. This object contains a Boolean `success` field and an optional `error` field that can contain an error message.

Angular provides some builders that are used by the CLI for commands such as `ng build` and `ng test`. Default target configurations for these and other built-in CLI builders can be found and configured in the "architect" section of the [workspace configuration file](reference/configs/workspace-config), `angular.json`. Also, extend and customize Angular by creating your own builders, which you can run directly using the [`ng run` CLI command](cli/run).

### [Builder project structure](#builder-project-structure)

A builder resides in a "project" folder that is similar in structure to an Angular workspace, with global configuration files at the top level, and more specific configuration in a source folder with the code files that define the behavior. For example, your `myBuilder` folder could contain the following files.

| Files                    | Purpose                                                                                      |
| :----------------------- | :------------------------------------------------------------------------------------------- |
| `src/my-builder.ts`      | Main source file for the builder definition.                                                 |
| `src/my-builder.spec.ts` | Source file for tests.                                                                       |
| `src/schema.json`        | Definition of builder input options.                                                         |
| `builders.json`          | Builders definition.                                                                         |
| `package.json`           | Dependencies. See <https://docs.npmjs.com/files/package.json>.                               |
| `tsconfig.json`          | [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html). |

Builders can be published to `npm`, see [Publishing your Library](tools/libraries/creating-libraries).

## [Creating a builder](#creating-a-builder)

As an example, create a builder that copies a file to a new location. To create a builder, use the `createBuilder()` CLI Builder function, and return a `Promise<BuilderOutput>` object.

### src/my-builder.ts (builder skeleton)

```
import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';
import {JsonObject} from '@angular-devkit/core';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
}
```

Now let's add some logic to it. The following code retrieves the source and destination file paths from user options and copies the file from the source to the destination (using the [Promise version of the built-in Node.js `copyFile()` function](https://nodejs.org/api/fs.html#fs_fspromises_copyfile_src_dest_mode)). If the copy operation fails, it returns an error with a message about the underlying problem.

### src/my-builder.ts (builder)

```
import {BuilderContext, BuilderOutput, createBuilder} from '@angular-devkit/architect';
import {JsonObject} from '@angular-devkit/core';
import {promises as fs} from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(options: Options, context: BuilderContext): Promise<BuilderOutput> {
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    return {
      success: false,
      error: (err as Error).message,
    };
  }
  return {success: true};
}
```

### [Handling output](#handling-output)

By default, `copyFile()` does not print anything to the process standard output or error. If an error occurs, it might be difficult to understand exactly what the builder was trying to do when the problem occurred. Add some additional context by logging additional information using the `Logger` API. This also lets the builder itself be executed in a separate process, even if the standard output and error are deactivated.

You can retrieve a `Logger` instance from the context.

### src/my-builder.ts (handling output)

```
try {
  await fs.copyFile(options.source, options.destination);
} catch (err) {
  context.logger.error('Failed to copy file.');
  return {
    success: false,
    error: (err as Error).message,
  };
}
```

### [Progress and status reporting](#progress-and-status-reporting)

The CLI Builder API includes progress and status reporting tools, which can provide hints for certain functions and interfaces.

To report progress, use the `context.reportProgress()` method, which takes a current value, optional total, and status string as arguments. The total can be any number. For example, if you know how many files you have to process, the total could be the number of files, and current should be the number processed so far. The status string is unmodified unless you pass in a new string value.

In our example, the copy operation either finishes or is still executing, so there's no need for a progress report, but you can report status so that a parent builder that called our builder would know what's going on. Use the `context.reportStatus()` method to generate a status string of any length.

**HELPFUL:** There's no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it.

Pass an empty string to remove the status.

### src/my-builder.ts (progress reporting)

```
context.reportStatus(`Copying ${options.source} to ${options.destination}.`);
try {
  await fs.copyFile(options.source, options.destination);
} catch (err) {
  context.logger.error('Failed to copy file.');
  return {
    success: false,
    error: (err as Error).message,
  };
}

context.reportStatus('Done.');
return {success: true};
```

## [Builder input](#builder-input)

You can invoke a builder indirectly through a CLI command such as `ng build`, or directly with the Angular CLI `ng run` command. In either case, you must provide required inputs, but can let other inputs default to values that are pre-configured for a specific *target*, specified by a [configuration](tools/cli/environments), or set on the command line.

### [Input validation](#input-validation)

You define builder inputs in a JSON schema associated with that builder. Similar to schematics, the Architect tool collects the resolved input values into an `options` object, and validates their types against the schema before passing them to the builder function.

For our example builder, `options` should be a `JsonObject` with two keys: a `source` and a `destination`, each of which are a string.

You can provide the following schema for type validation of these values.

### schema.json

```
{
  "$schema": "https://json-schema.org/schema",
  "type": "object",
  "properties": {
    "source": {
      "type": "string"
    },
    "destination": {
      "type": "string"
    }
  }
}
```

**HELPFUL:** This is a minimal example, but the use of a schema for validation can be very powerful. For more information, see the [JSON schemas website](https://json-schema.org).

To link our builder implementation with its schema and name, you need to create a *builder definition* file, which you can point to in `package.json`.

Create a file named `builders.json` that looks like this:

### builders.json

```
{
  "builders": {
    "copy": {
      "implementation": "./dist/my-builder.js",
      "schema": "./src/schema.json",
      "description": "Copies a file."
    }
  }
}
```

In the `package.json` file, add a `builders` key that tells the Architect tool where to find our builder definition file.

### package.json

```
{
  "name": "@example/copy-file",
  "version": "1.0.0",
  "description": "Builder for copying files",
  "builders": "builders.json",
  "dependencies": {
    "@angular/build": "^21.2.0"
  }
}
```

The official name of our builder is now `@example/copy-file:copy`. The first part of this is the package name and the second part is the builder name as specified in the `builders.json` file.

These values are accessed on `options.source` and `options.destination`.

### src/my-builder.ts (report status)

```
await fs.copyFile(options.source, options.destination);
```

### [Target configuration](#target-configuration)

A builder must have a defined target that associates it with a specific input configuration and project.

Targets are defined in the `angular.json` [CLI configuration file](reference/configs/workspace-config). A target specifies the builder to use, its default options configuration, and named alternative configurations. Architect in the Angular CLI uses the target definition to resolve input options for a given run.

The `angular.json` file has a section for each project, and the "architect" section of each project configures targets for builders used by CLI commands such as 'build', 'test', and 'serve'. By default, for example, the `ng build` command runs the builder `@angular/build:application` to perform the build task, and passes in default option values as specified for the `build` target in `angular.json`.

### angular.json

```
{
  "myApp": {
    "...": "...",
    "architect": {
      "build": {
        "builder": "@angular/build:application",
        "options": {
          "outputPath": "dist/myApp",
          "index": "src/index.html",
          "...": "..."
        },
        "configurations": {
          "production": {
            "fileReplacements": [
              {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
              }
            ],
            "optimization": true,
            "outputHashing": "all",
            "...": "..."
          }
        }
      },
      "...": "..."
    }
  }
}
```

The command passes the builder the set of default options specified in the "options" section. If you pass the `--configuration=production` flag, it uses the override values specified in the `production` configuration. Specify further option overrides individually on the command line.

#### [Target strings](#target-strings)

The generic `ng run` CLI command takes as its first argument a target string of the following form.

```
project:target[:configuration]
```

|               | Details                                                                                                               |
| :------------ | :-------------------------------------------------------------------------------------------------------------------- |
| project       | The name of the Angular CLI project that the target is associated with.                                               |
| target        | A named builder configuration from the `architect` section of the `angular.json` file.                                |
| configuration | (optional) The name of a specific configuration override for the given target, as defined in the `angular.json` file. |

If your builder calls another builder, it might need to read a passed target string. Parse this string into an object by using the `targetFromTargetString()` utility function from `@angular-devkit/architect`.

## [Schedule and run](#schedule-and-run)

Architect runs builders asynchronously. To invoke a builder, you schedule a task to be run when all configuration resolution is complete.

The builder function is not executed until the scheduler returns a `BuilderRun` control object. The CLI typically schedules tasks by calling the `context.scheduleTarget()` function, and then resolves input options using the target definition in the `angular.json` file.

Architect resolves input options for a given target by taking the default options object, then overwriting values from the configuration, then further overwriting values from the overrides object passed to `context.scheduleTarget()`. For the Angular CLI, the overrides object is built from command line arguments.

Architect validates the resulting options values against the schema of the builder. If inputs are valid, Architect creates the context and executes the builder.

For more information see [Workspace Configuration](reference/configs/workspace-config).

**HELPFUL:** You can also invoke a builder directly from another builder or test by calling `context.scheduleBuilder()`. You pass an `options` object directly to the method, and those option values are validated against the schema of the builder without further adjustment.

Only the `context.scheduleTarget()` method resolves the configuration and overrides through the `angular.json` file.

### [Default architect configuration](#default-architect-configuration)

Let's create a simple `angular.json` file that puts target configurations into context.

You can publish the builder to npm (see [Publishing your Library](tools/libraries/creating-libraries#publishing-your-library)), and install it using the following command:

```
npm install @example/copy-file
```

If you create a new project with `ng new builder-test`, the generated `angular.json` file looks something like this, with only default builder configurations.

### angular.json

```
{
  "projects": {
    "builder-test": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "outputPath": "dist/builder-test",
            "index": "src/index.html",
            "browser": "src/main.ts",
            "tsConfig": "tsconfig.app.json"
          },
          "configurations": {
            "production": {
              "optimization": true,
              "aot": true
            }
          }
        }
      }
    }
  }
}
```

### [Adding a target](#adding-a-target)

Add a new target that will run our builder to copy a file. This target tells the builder to copy the `package.json` file.

* We will add a new target section to the `architect` object for our project

* The target named `copy-package` uses our builder, which you published to `@example/copy-file`.

* The options object provides default values for the two inputs that you defined.

  * `source` - The existing file you are copying.
  * `destination` - The path you want to copy to.

### angular.json

```
{
  "projects": {
    "builder-test": {
      "architect": {
        "copy-package": {
          "builder": "@example/copy-file:copy",
          "options": {
            "source": "package.json",
            "destination": "package-copy.json"
          }
        }
        // Existing targets...
      }
    }
  }
}
```

### [Running the builder](#running-the-builder)

To run our builder with the new target's default configuration, use the following CLI command.

```
ng run builder-test:copy-package
```

This copies the `package.json` file to `package-copy.json`.

Use command-line arguments to override the configured defaults. For example, to run with a different `destination` value, use the following CLI command.

```
ng run builder-test:copy-package --destination=package-other.json
```

This copies the file to `package-other.json` instead of `package-copy.json`. Because you did not override the *source* option, it will still copy from the default `package.json` file.

## [Testing a builder](#testing-a-builder)

Use integration testing for your builder, so that you can use the Architect scheduler to create a context, as in this [example](https://github.com/mgechev/cli-builders-demo). In the builder source directory, create a new test file `my-builder.spec.ts`. The test creates new instances of `JsonSchemaRegistry` (for schema validation), `TestingArchitectHost` (an in-memory implementation of `ArchitectHost`), and `Architect`.

Here's an example of a test that runs the copy file builder. The test uses the builder to copy the `package.json` file and validates that the copied file's contents are the same as the source.

### src/my-builder.spec.ts

```
import {Architect} from '@angular-devkit/architect';
import {TestingArchitectHost} from '@angular-devkit/architect/testing';
import {schema} from '@angular-devkit/core';
import {promises as fs} from 'fs';
import {join} from 'path';

describe('Copy File Builder', () => {
  let architect: Architect;
  let architectHost: TestingArchitectHost;

  beforeEach(async () => {
    const registry = new schema.CoreSchemaRegistry();
    registry.addPostTransform(schema.transforms.addUndefinedDefaults);

    // TestingArchitectHost() takes workspace and current directories.
    // Since we don't use those, both are the same in this case.
    architectHost = new TestingArchitectHost(__dirname, __dirname);
    architect = new Architect(architectHost, registry);

    // This will either take a Node package name, or a path to the directory
    // for the package.json file.
    await architectHost.addBuilderFromPackage(join(__dirname, '..'));
  });

  it('can copy files', async () => {
    // A "run" can have multiple outputs, and contains progress information.
    const run = await architect.scheduleBuilder('@example/copy-file:copy', {
      source: 'package.json',
      destination: 'package-copy.json',
    });

    // The "result" member (of type BuilderOutput) is the next output.
    const output = await run.result;

    // Stop the builder from running. This stops Architect from keeping
    // the builder-associated states in memory, since builders keep waiting
    // to be scheduled.
    await run.stop();

    // Expect that the copied file is the same as its source.
    const sourceContent = await fs.readFile('package.json', 'utf8');
    const destinationContent = await fs.readFile('package-copy.json', 'utf8');
    expect(destinationContent).toBe(sourceContent);
  });
});
```

**HELPFUL:** When running this test in your repo, you need the [`ts-node`](https://github.com/TypeStrong/ts-node) package. You can avoid this by renaming `my-builder.spec.ts` to `my-builder.spec.js`.

### [Watch mode](#watch-mode)

Most builders to run once and return. However, this behavior is not entirely compatible with a builder that watches for changes (like a devserver, for example). Architect can support watch mode, but there are some things to look out for.

* To be used with watch mode, a builder handler function should return an `Observable`. Architect subscribes to the `Observable` until it completes and might reuse it if the builder is scheduled again with the same arguments.

* The builder should always emit a `BuilderOutput` object after each execution. Once it's been executed, it can enter a watch mode, to be triggered by an external event. If an event triggers it to restart, the builder should execute the `context.reportRunning()` function to tell Architect that it is running again. This prevents Architect from stopping the builder if another run is scheduled.

When your builder calls `BuilderRun.stop()` to exit watch mode, Architect unsubscribes from the builder's `Observable` and calls the builder's teardown logic to clean up. This behavior also allows for long-running builds to be stopped and cleaned up.

In general, if your builder is watching an external event, you should separate your run into three phases.

| Phases     | Details                                                                                                                                                                                                                                       |
| :--------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Running    | The task being performed, such as invoking a compiler. This ends when the compiler finishes and your builder emits a `BuilderOutput` object.                                                                                                  |
| Watching   | Between two runs, watch an external event stream. For example, watch the file system for any changes. This ends when the compiler restarts, and `context.reportRunning()` is called.                                                          |
| Completion | Either the task is fully completed, such as a compiler which needs to run a number of times, or the builder run was stopped (using `BuilderRun.stop()`). Architect executes teardown logic and unsubscribes from your builder's `Observable`. |

## [Summary](#summary)

The CLI Builder API provides a means of changing the behavior of the Angular CLI by using builders to execute custom logic.

* Builders can be synchronous or asynchronous, execute once or watch for external events, and can schedule other builders or targets.
* Builders have option defaults specified in the `angular.json` configuration file, which can be overwritten by an alternate configuration for the target, and further overwritten by command line flags
* The Angular team recommends that you use integration tests to test Architect builders. Use unit tests to validate the logic that the builder executes.
* If your builder returns an `Observable`, it should clean up the builder in the teardown logic of that `Observable`.

----
url: https://angular.dev/api/cdk/testing/ContentContainerComponentHarness
----

```
    
      abstract class ContentContainerComponentHarness<S extends string = string> extends ComponentHarness implements HarnessLoader {}
    
    
```

Gets a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) that searches for harnesses under the first element matching the given selector within the current harness's content.

@paramselector`S`

The selector for an element in the component's content.

@returns`Promise<HarnessLoader>`

A [`HarnessLoader`](/api/cdk/testing/HarnessLoader) that searches for harnesses under the given selector.

Gets a list of [`HarnessLoader`](/api/cdk/testing/HarnessLoader) for each element matching the given selector under the current harness's cotnent that searches for harnesses under that element.

@paramselector`S`

The selector for elements in the component's content.

@returns`Promise<HarnessLoader[]>`

A list of [`HarnessLoader`](/api/cdk/testing/HarnessLoader) for each element matching the given selector.

Gets the first matching harness for the given query within the current harness's content.

@paramquery`HarnessQuery<T>`

The harness query to search for.

@returns`Promise<T>`

The first harness matching the given query.

Gets the first matching harness for the given query within the current harness's content.

@paramquery`HarnessQuery<T>`

The harness query to search for.

@returns`Promise<T | null>`

The first harness matching the given query, or null if none is found.

Gets a matching harness for the given query and index within the current harness's content.

@paramquery`HarnessQuery<T>`

The harness query to search for.

@paramindex`number`

The zero-indexed offset of the component to find.

@returns`Promise<T>`

The first harness matching the given query.

Gets all matching harnesses for the given query within the current harness's content.

@paramquery`HarnessQuery<T>`

The harness query to search for.

@returns`Promise<T[]>`

The list of harness matching the given query.

Returns the number of matching harnesses for the given query within the current harness's content.

@paramquery`HarnessQuery<T>`

The harness query to search for.

@returns`Promise<number>`

The number of matching harnesses for the given query.

Checks whether there is a matching harnesses for the given query within the current harness's content.

@paramquery`HarnessQuery<T>`

The harness query to search for.

@returns`Promise<boolean>`

Whether there is matching harnesses for the given query.

Gets the root harness loader from which to start searching for content contained by this harness.

@returns`Promise<HarnessLoader>`

Gets a `Promise` for the [`TestElement`](/api/cdk/testing/TestElement) representing the host element of the component.

@returns`Promise<TestElement>`

Gets a [`LocatorFactory`](/api/cdk/testing/LocatorFactory) for the document root element. This factory can be used to create locators for elements that a component creates outside of its own root element. (e.g. by appending to document.body).

@returns`LocatorFactory`

Creates an asynchronous locator function that can be used to find a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instance or element under the host element of this [`ComponentHarness`](/api/cdk/testing/ComponentHarness).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await ch.locatorFor(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await ch.locatorFor('div', DivHarness)() // Gets a `TestElement` instance for #d1
await ch.locatorFor('span')()            // Throws because the `Promise` rejects
```

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await ch.locatorForOptional(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await ch.locatorForOptional('div', DivHarness)() // Gets a `TestElement` instance for #d1
await ch.locatorForOptional('span')()            // Gets `null`
```

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
// Gets [DivHarness for #d1, TestElement for #d1, DivHarness for #d2, TestElement for #d2]
await ch.locatorForAll(DivHarness, 'div')()
// Gets [TestElement for #d1, TestElement for #d2]
await ch.locatorForAll('div', '#d1')()
// Gets [DivHarness for #d1, IdIsD1Harness for #d1, DivHarness for #d2]
await ch.locatorForAll(DivHarness, IdIsD1Harness)()
// Gets []
await ch.locatorForAll('span')()
```

----
url: https://angular.dev/api/core/SimpleChange
----

```
    
      class SimpleChange<T = any> {}
    
    
```

@parampreviousValue`T`

@paramcurrentValue`T`

@paramfirstChange`boolean`

@returns`SimpleChange<T>`

Check whether the new value is the first value assigned.

@returns`boolean`

Jump to details

----
url: https://angular.dev/api/common/http/withInterceptors
----

```
function withInterceptors(
  interceptorFns: HttpInterceptorFn[],
): HttpFeature<HttpFeatureKind.Interceptors>;
```

@paraminterceptorFns`HttpInterceptorFn[]`

@returns`HttpFeature<HttpFeatureKind.Interceptors>`

Jump to details

----
url: https://angular.dev/api/ssr/node/createNodeRequestHandler
----

```
function createNodeRequestHandler<T extends NodeRequestHandlerFunction>(
  handler: T,
): T;
```

@paramhandler`T`

* The handler function to be defined and annotated.

@returns`T`

The same handler function passed as an argument, with metadata attached.

## [Usage Notes](#usage-notes)

Usage in an Express application:

```
const app = express();
export default createNodeRequestHandler(app);
```

Usage in a Hono application:

```
const app = new Hono();
export default createNodeRequestHandler(async (req, res, next) => {
  try {
    const webRes = await app.fetch(createWebRequestFromNodeRequest(req));
    if (webRes) {
      await writeResponseToNodeResponse(webRes, res);
    } else {
      next();
    }
  } catch (error) {
    next(error);
  }
});
```

Usage in a Fastify application:

```
const app = Fastify();
export default createNodeRequestHandler(async (req, res) => {
  await app.ready();
  app.server.emit('request', req, res);
});
```

Jump to details

----
url: https://angular.dev/guide/performance
----

In-depth Guides

Server-side & hybrid-rendering

# Server-side & hybrid rendering

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/performance/overview.md "Edit this page")

Learn about different ways you can optimize the performance of your application with different rendering strategies.

One of the top priorities of any developer is ensuring that their application is as performant as possible. These guides are here to help you follow best practices for building performant applications by taking advantage of different rendering strategies.

| Guides Types                                          | Description                                                                                                                                                                             |
| :---------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Server and hybrid rendering](/guide/ssr)             | Learn how to leverage rendering pages on the server to improve load times.                                                                                                              |
| [Hydration](/guide/hydration)                         | A process to improve application performance by restoring its state after server-side rendering and reusing existing DOM structure as much as possible.                                 |
| [Incremental Hydration](/guide/incremental-hydration) | Incremental hydration is an advanced type of hydration that can leave sections of your application dehydrated and incrementally trigger hydration of those sections as they are needed. |

----
url: https://angular.dev/api/forms/signals/MetadataKey
----

```
    
      class MetadataKey<TRead, TWrite, TAcc> {}
    
    
```

Use [`createMetadataKey`](/api/forms/signals/createMetadataKey).

@paramreducer`MetadataReducer<TAcc, TWrite>`

@paramcreate`((state: FieldState<unknown, string | number>, data: Signal<TAcc>) => TRead) | undefined`

@returns`MetadataKey<TRead, TWrite, TAcc>`

Jump to details

----
url: https://angular.dev/api/common/TranslationWidth
----

locale data getters are deprecated

## [API](#api)

```
    
      enum TranslationWidth {}
    
    
```

1 character for `en-US`. For example: 'S'

3 characters for `en-US`. For example: 'Sun'

Full length for `en-US`. For example: "Sunday"

2 characters for `en-US`, For example: "Su"

Jump to details

----
url: https://angular.dev/api/common/http/HttpTransferCacheOptions
----

```
    
      interface HttpTransferCacheOptions {}
    
    
```

A function that receives a request as an argument and returns a boolean to indicate whether a request should be included into the cache.

Specifies which headers should be included into cached responses. No headers are included by default.

Enables caching for `POST` requests. By default, only `GET` and `HEAD` requests are cached. This option can be enabled if `POST` requests are used to retrieve data (for example using `GraphQL`).

Enables caching of requests containing `Authorization`, `Proxy-Authorization`, or `Cookie` headers. By default, these requests are excluded from caching.

Enables caching of requests sent using `withCredentials` or Fetch API `credentials` modes (`include` or `same-origin`). By default, these requests are excluded from caching.

Enables caching of requests and responses with `Cache-Control` directives that forbid caching (such as `no-cache`, `no-store`, or `private`), responses with a `Set-Cookie` header, or requests using Fetch API `no-cache` or `no-store` modes. By default, these requests/responses are excluded from caching.

Jump to details

----
url: https://angular.dev/api/core/testing/ModuleTeardownOptions
----

```
    
      interface ModuleTeardownOptions {}
    
    
```

Whether the test module should be destroyed after every test. Defaults to `true`.

Whether errors during test module destruction should be re-thrown. Defaults to `true`.

Jump to details

----
url: https://angular.dev/api/forms/signals/RemoveStringIndexUnknownKey
----

```
    
      type RemoveStringIndexUnknownKey<K, V> = string extends K  ? unknown extends V    ? never    : K  : K
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/PathKind
----

```
    
      type PathKind = PathKind.Root | PathKind.Child | PathKind.Item
    
    
```

The [`PathKind`](/api/forms/signals/PathKind) for a `FieldPath` that is at the root of its field tree.

```
    
      interface Root {}
    
    
```

The [`PathKind`](/api/forms/signals/PathKind) for a `FieldPath` that is a child of another `FieldPath`.

```
    
      interface Child extends PathKind.Root {}
    
    
```

The [`PathKind`](/api/forms/signals/PathKind) for a `FieldPath` that is an item in a `FieldPath` array.

```
    
      interface Item extends PathKind.Child {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/aria/overview
----

In-depth Guides

Angular Aria

# Angular Aria

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/aria/overview.md "Edit this page")

## [What is Angular Aria?](#what-is-angular-aria)

Building accessible components seems straightforward, but implementing them according to the [W3C Accessibility Guidelines](https://www.w3.org/TR/wcag/) requires significant effort and accessibility expertise.

Angular Aria is a collection of headless, accessible directives that implement common [WAI-ARIA patterns](https://www.w3.org/WAI/ARIA/apg/patterns/). The directives handle keyboard interactions, ARIA attributes, focus management, and screen reader support. All you have to do is provide the HTML structure, CSS styling, and business logic!

## [Installation](#installation)

### npm

```
npm install @angular/aria
```

### yarn

```
yarn add @angular/aria
```

### pnpm

```
pnpm add @angular/aria
```

### bun

```
bun add @angular/aria
```

## [Showcase](#showcase)

For example, let's take a toolbar menu. While it may appear to be a "simple" row of buttons tied with specific logic, keyboard navigation and screen readers add a lot of unexpected complexity to those unfamiliar with accessibility.

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 0.5rem 1rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
}

.group {
  gap: 0.5rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  cursor: pointer;
  padding: 0.5rem;
  border-radius: 4px;
  font-size: 1.25rem;
  background-color: transparent;
  color: var(--primary-contrast);
}

[ngToolbarWidget]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngToolbarWidget]:active {
  background-color: color-mix(in srgb, var(--primary-contrast) 15%, transparent);
}

[ngToolbarWidget]:focus {
  outline-offset: -1px;
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngToolbarWidget][aria-pressed="true"],
[ngToolbarWidget][aria-checked="true"] {
  color: color-mix(in srgb, var(--hot-pink) 80%, var(--primary-contrast));
  background-color: color-mix(in srgb, var(--hot-pink) 10%, transparent);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="material-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
}

[ngToolbar] {
  display: flex;
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
  border-radius: 2rem;
  color: color-mix(in srgb, var(--vivid-pink) 70%, var(--full-contrast));
  background-color: color-mix(in srgb, var(--vivid-pink) 20%, transparent);
  transition:
    width 0.2s ease-in-out,
    background-color 0.15s ease-in-out,
    color 0.15s ease-in-out;
}

[ngToolbarWidget]:focus {
  outline-offset: 2px;
  outline: 2px solid var(--vivid-pink);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  background-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--full-contrast));
  color: var(--page-background);
}

[ngToolbarWidget][aria-checked='true'] {
  width: 4.5rem;
}

.group {
  display: flex;
  gap: 0.5rem;
}

.separator {
  width: 1px;
  margin: 0 1rem;
  height: calc(100% - 2rem);
  background-color: var(--quinary-contrast);
}
```

### app.ts

```
import {Component, signal} from '@angular/core';
import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup],
})
export class App {
  readonly bold = signal(false);
  readonly italic = signal(false);
  readonly underlined = signal(false);
  readonly alignment = signal<'left' | 'center' | 'right'>('left');
}
```

### app.html

```
<div ngToolbar class="retro-toolbar" aria-label="Text Formatting Tools">
  <div class="group">
    <button
      ngToolbarWidget
      value="undo"
      type="button"
      aria-label="undo"
      class="material-symbols-outlined"
      translate="no"
    >
      undo
    </button>

    <button
      ngToolbarWidget
      value="redo"
      type="button"
      aria-label="redo"
      class="material-symbols-outlined"
      translate="no"
    >
      redo
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div class="group">
    <button
      ngToolbarWidget
      value="bold"
      type="button"
      aria-label="bold"
      class="material-symbols-outlined"
      [attr.aria-pressed]="bold()"
      (click)="bold.set(!bold())"
      translate="no"
    >
      format_bold
    </button>

    <button
      ngToolbarWidget
      value="italic"
      type="button"
      aria-label="italic"
      class="material-symbols-outlined"
      [attr.aria-pressed]="italic()"
      (click)="italic.set(!italic())"
      translate="no"
    >
      format_italic
    </button>

    <button
      ngToolbarWidget
      value="underlined"
      type="button"
      aria-label="underlined"
      class="material-symbols-outlined"
      [attr.aria-pressed]="underlined()"
      (click)="underlined.set(!underlined())"
      translate="no"
    >
      format_underlined
    </button>
  </div>

  <div class="separator" role="separator"></div>

  <div ngToolbarWidgetGroup role="radiogroup" class="group" aria-label="Text alignment options">
    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align left"
      aria-label="align left"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'left'"
      (click)="alignment.set('left')"
      translate="no"
    >
      format_align_left
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align center"
      aria-label="align center"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'center'"
      (click)="alignment.set('center')"
      translate="no"
    >
      format_align_center
    </button>

    <button
      ngToolbarWidget
      role="radio"
      type="button"
      value="align right"
      aria-label="align right"
      class="material-symbols-outlined"
      [attr.aria-checked]="alignment() === 'right'"
      (click)="alignment.set('right')"
      translate="no"
    >
      format_align_right
    </button>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background));
  --retro-pressed-tint: #fbfbfb;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

:host-context(.docs-dark-mode) {
  --retro-pressed-tint: #151417;
}

[ngToolbar] {
  gap: 1.5rem;
  display: flex;
  padding: 1rem;
}

.group {
  gap: 1rem;
  display: flex;
}

.separator {
  width: 1px;
  align-self: center;
  height: calc(100% - 1rem);
  background-color: var(--quinary-contrast);
}

[ngToolbarWidget] {
  border: none;
  outline: none;
  cursor: pointer;
  padding: 0.5rem;
  font-size: 1.25rem;
  color: var(--page-background);

  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngToolbarWidget]:focus,
[ngToolbarWidget]:hover {
  transform: translate(1px, 1px);
}

[ngToolbarWidget]:active,
[ngToolbarWidget][aria-pressed='true'],
[ngToolbarWidget][aria-checked='true'] {
  transform: translate(4px, 4px);
  box-shadow: var(--retro-pressed-shadow);
  background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--retro-pressed-tint));
}

[ngToolbarWidget]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--retro-button-color);
}
```

In this one scenario, developers need to consider:

* **Keyboard navigation**. Users need to open the menu with Enter or Space, navigate options with arrow keys, select with Enter, and close with Escape.
* **Screen readers** need to announce the menu's state, the number of options, and which option has focus.
* **Focus management** needs to move logically between the trigger and menu items.
* **Right-to-left languages** require the ability to navigate in reverse.

## [What's included?](#whats-included)

Angular Aria includes directives with comprehensive documentation, working examples and API references for common interactive patterns:

### [Search and selection](#search-and-selection)

| Component                               | Description                                                    |
| --------------------------------------- | -------------------------------------------------------------- |
| [Autocomplete](guide/aria/autocomplete) | Text input with filtered suggestions that appear as users type |
| [Listbox](guide/aria/listbox)           | Single or multi-select option lists with keyboard navigation   |
| [Select](guide/aria/select)             | Single-selection dropdown pattern with keyboard navigation     |
| [Multiselect](guide/aria/multiselect)   | Multiple-selection dropdown pattern with compact display       |
| [Combobox](guide/aria/combobox)         | Primitive directive that coordinates a text input with a popup |

### [Navigation and call to actions](#navigation-and-call-to-actions)

| Component                     | Description                                                |
| ----------------------------- | ---------------------------------------------------------- |
| [Menu](guide/aria/menu)       | Dropdown menus with nested submenus and keyboard shortcuts |
| [Menubar](guide/aria/menubar) | Horizontal navigation bar for persistent application menus |
| [Toolbar](guide/aria/toolbar) | Grouped sets of controls with logical keyboard navigation  |

### [Content organization](#content-organization)

| Component                         | Description                                                            |
| --------------------------------- | ---------------------------------------------------------------------- |
| [Accordion](guide/aria/accordion) | Collapsible content panels that can expand individually or exclusively |
| [Tabs](guide/aria/tabs)           | Tabbed interfaces with automatic or manual activation modes            |
| [Tree](guide/aria/tree)           | Hierarchical lists with expand/collapse functionality                  |
| [Grid](guide/aria/grid)           | Two-dimensional data display with cell-by-cell keyboard navigation     |

## [When to use Angular Aria](#when-to-use-angular-aria)

Angular Aria works well when you need accessible interactive components that are WCAG compliant with custom styling. Examples include:

* **Building a design system** - Your team maintains a component library with specific visual standards that need accessible implementations
* **Enterprise component libraries** - You're creating reusable components for multiple applications within an organization
* **Custom brand requirements** - The interface needs to match precise design specifications that pre-styled component libraries cannot easily accommodate

## [When not to use Angular Aria](#when-not-to-use-angular-aria)

Angular Aria might not fit every scenario:

* **Pre-styled components** - If you need components that look complete without custom styling, use Angular Material instead
* **Simple forms** - Native HTML form controls like `<button>` and `<input type="radio">` provide built-in accessibility for straightforward use cases
* **Rapid prototyping** - When validating concepts quickly, pre-styled component libraries reduce initial development time

## [Next steps](#next-steps)

Check out a component from the side nav or [list above](#whats-included), or start with [Toolbar](guide/aria/toolbar) to see a complete example of how Angular Aria directives work!

----
url: https://angular.dev/cli/generate/library
----

```
ng generate library [name] [options]
```

```
ng generate lib [name] [options]
```

Creates a new library project in your Angular workspace. Libraries are reusable collections of components, services, and other Angular artifacts that can be shared across multiple applications. This schematic simplifies the process of generating a new library with the necessary files and configurations.

## [Arguments](#arguments)

`name`

The name for the new library. This name will be used for the project directory and various identifiers within the library's code.

Value Type`string`

## [Options](#options)

`entry-file`

The path to the library's public API file, relative to the workspace root. This file defines what parts of the library are accessible to applications that import it.

Value Type`string`Default`public-api`

`prefix`

Alias`p`

A prefix to be added to the selectors of components generated within this library. For example, if the prefix is `my-lib` and you generate a component named `my-component`, the selector will be `my-lib-my-component`.

Value Type`string`Default`lib`

`project-root`

The root directory for the new library, relative to the workspace root. If not specified, the library will be created in a subfolder within the `projects` directory, using the library's name.

Value Type`string`

`skip-install`

Skip the automatic installation of packages. You will need to manually install the dependencies later.

Value Type`boolean`Default`false`

`skip-package-json`

Do not automatically add dependencies to the `package.json` file.

Value Type`boolean`Default`false`

`skip-ts-config`

Do not update the workspace `tsconfig.json` file to add a path mapping for the new library. The path mapping is needed to use the library in an application, but can be disabled here to simplify development.

Value Type`boolean`Default`false`

`standalone`

Create a library that utilizes the standalone API, eliminating the need for NgModules. This can simplify the structure of your library and its usage in applications.

Value Type`boolean`Default`true`

`test-runner`

The unit testing runner to use.

Value Type`string`Allowed Values`karma`, `vitest`Default`vitest`

Jump to details

----
url: https://angular.dev/guide/di
----

In-depth Guides

Dependency Injection

# Dependency injection in Angular

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/di/overview.md "Edit this page")

Dependency Injection (DI) is a design pattern you use to organize and share code across your application by supplying dependencies to a class instead of creating them inside it.

**TIP:** Check out Angular's [Essentials](essentials/dependency-injection) before diving into this comprehensive guide.

As an application grows, developers often need to reuse and share functionality across different parts of the codebase. [Dependency Injection (DI)](https://en.wikipedia.org/wiki/Dependency_injection) helps you achieve this by allowing you to provide dependencies to a class instead of creating them directly inside it. This makes different parts of the application more reusable and easier to manage.

Dependency injection is a popular pattern because it allows developers to address common challenges such as:

* **Improved code maintainability**: Dependency injection promotes a clear separation of concerns, making code easier to refactor and reducing duplication.
* **Scalability**: You can reuse modular functionality across different parts of an application, making it easier to scale.
* **Better testing**: DI allows unit tests to use [test doubles](https://en.wikipedia.org/wiki/Test_double) in place of real implementations when needed.

## [How does dependency injection work in Angular?](#how-does-dependency-injection-work-in-angular)

A dependency is any object, value, function, or service that a class requires to work but does not create itself. Instead, you provide it from the outside, creating a clear relationship between different parts of the application.

You interact with a dependency injection system in two main ways:

* You can *provide*, or make available, values.
* You can *inject*, or ask for, those values as dependencies.

In this context, "values" can refer to any JavaScript value, including objects, functions, or class instances. Common types of injected dependencies include:

* **Configuration values**: Environment-specific constants, API URLs, feature flags, etc.
* **Factories**: Functions that create objects or values based on runtime conditions
* **Services**: Classes that provide common functionality, business logic, or state

Angular components and directives automatically participate in DI, meaning that you can inject dependencies into them and make them available for injection.

## [What are services?](#what-are-services)

An Angular *service* is a TypeScript class decorated with [`@Service`](/api/core/Service), which allows you to inject an instance of the class as a dependency. Services are the most common way of sharing data and functionality across an application.

Common types of services include:

* **Data clients:** Abstracts the details of making requests to a server for data retrieval and mutation
* **State management:** Defines state shared across multiple components or pages
* **Authentication and authorization:** Manages user authentication, token storage, and access control
* **Logging and error handling:** Establishes a common API for logging or communicating error states to the user
* **Event handling and dispatch:** Handles events or notifications that are not associated with a specific component, or for dispatching events and notifications to components, following the [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern)
* **Utility functions:** Offers reusable utility functions like data formatting, validation, or calculations

The following example declares a service named `AnalyticsLogger`:

```
import {Service} from '@angular/core';

@Service()
export class AnalyticsLogger {
  trackEvent(category: string, value: string) {
    console.log('Analytics event logged:', {
      category,
      value,
      timestamp: new Date().toISOString(),
    });
  }
}
```

**NOTE:** The [`@Service`](/api/core/Service) makes this service available throughout your entire application as a singleton. This is the recommended approach for most services.

**HELPFUL:** The [`@Service`](guide/di/creating-and-using-services#using-the-service-vs-injectable-decorator) decorator is an ergonomic shorthand for `@Injectable({providedIn: 'root'})`.

## [Injecting dependencies with `inject()`](#injecting-dependencies-with-inject)

You can inject dependencies using Angular's `inject()` function.

Here is an example of a navigation bar that injects `AnalyticsLogger` and Angular [`Router`](/api/router/Router) service to allow users to navigate to a different page while tracking the event.

```
import {Component, inject} from '@angular/core';
import {Router} from '@angular/router';
import {AnalyticsLogger} from './analytics-logger';

@Component({
  selector: 'app-navbar',
  template: `<a href="#" (click)="navigateToDetail($event)">Detail Page</a>`,
})
export class Navbar {
  private router = inject(Router);
  private analytics = inject(AnalyticsLogger);

  navigateToDetail(event: Event) {
    event.preventDefault();
    this.analytics.trackEvent('navigation', '/details');
    this.router.navigate(['/details']);
  }
}
```

### [Where can `inject()` be used?](#where-can-inject-be-used)

You can inject dependencies during construction of a component, directive, or service. The call to [`inject`](/api/core/inject) can appear in either the `constructor` or in a field initializer. Here are some common examples:

```
@Component(/* ... */)
export class MyComponent {
  // ✅ In class field initializer
  private service = inject(MyService);

  // ✅ In constructor body
  private anotherService: MyService;

  constructor() {
    this.anotherService = inject(MyService);
  }
}
```

```
@Directive({...})
export class MyDirective {
  // ✅ In class field initializer
  private element = inject(ElementRef);
}
```

```
import {Service, inject} from '@angular/core';
import {HttpClient} from '@angular/common/http';

@Service()
export class MyService {
  // ✅ In a service
  private http = inject(HttpClient);
}
```

```
export const authGuard = () => {
  // ✅ In a route guard
  const auth = inject(AuthService);
  return auth.isAuthenticated();
};
```

Angular uses the term "injection context" to describe any place in your code where you can call [`inject`](/api/core/inject). While component, directive, and service construction is the most common, see [injection contexts](/guide/di/dependency-injection-context) for more details.

For more information, see the [inject API docs](api/core/inject#usage-notes).

## [Next steps](#next-steps)

Now that you understand the fundamentals of dependency injection in Angular, you're ready to learn how to create your own services.

The next guide, [Creating and using services](guide/di/creating-and-using-services), will show you:

* How to create a service with the Angular CLI or manually
* How the `providedIn: 'root'` pattern works
* How to inject services into components and other services

This covers the most common use case for services in Angular applications.

----
url: https://angular.dev/guide/signals/effect
----

Signals are useful because they notify interested consumers when they change. An **effect** is an operation that runs whenever one or more signal values change. You can create an effect with the [`effect`](/api/core/effect) function:

```
import {effect} from '@angular/core';

effect(() => {
  console.log(`The current count is: ${count()}`);
});
```

Effects always run **at least once.** When an effect runs, it tracks any signal value reads. Whenever any of these signal values change, the effect runs again. Similar to computed signals, effects keep track of their dependencies dynamically, and only track signals which were read in the most recent execution.

Effects always execute **asynchronously**, during the change detection process.

## [Use cases for effects](#use-cases-for-effects)

Effects should be the last API you reach for. Always prefer [`computed()`](/api/core/computed) for derived values and [`linkedSignal()`](/api/core/linkedSignal) for values that can be both derived and manually set. If you find yourself copying data from one signal to another with an effect, it's a sign you should move your source-of-truth higher up and use [`computed()`](/api/core/computed) or [`linkedSignal()`](/api/core/linkedSignal) instead. Effects are best for syncing signal state to imperative, non-signal APIs.

**TIP:** There are no situations where effect is good, only situations where it is appropriate.

* Logging signal values, either for analytics or as a debugging tool.
* Keeping data in sync with different kinds of storage: `window.localStorage`, session storage, cookies, etc.
* Adding custom DOM behavior that can't be expressed with template syntax.
* Performing custom rendering to a `<canvas>` element, charting library, or other third party UI library.

### When not to use effects

Avoid using effects for propagation of state changes. This can result in `ExpressionChangedAfterItHasBeenChecked` errors, infinite circular updates, or unnecessary change detection cycles.

Instead, use [`computed`](/api/core/computed) signals to model state that depends on other state.

## [Injection context](#injection-context)

By default, you can only create an [`effect()`](/api/core/effect) within an [injection context](guide/di/dependency-injection-context) (where you have access to the `inject` function). The easiest way to satisfy this requirement is to call [`effect`](/api/core/effect) within a component, directive, or service `constructor`:

```
@Component(/* ... */)
export class EffectiveCounter {
  readonly count = signal(0);

  constructor() {
    // Register a new effect.
    effect(() => {
      console.log(`The count is: ${this.count()}`);
    });
  }
}
```

To create an effect outside the constructor, you can pass an [`Injector`](/api/core/Injector) to [`effect`](/api/core/effect) via its options:

```
@Component(/* ... */)
export class EffectiveCounter {
  readonly count = signal(0);
  private injector = inject(Injector);

  initializeLogging(): void {
    effect(
      () => {
        console.log(`The count is: ${this.count()}`);
      },
      {injector: this.injector},
    );
  }
}
```

## [Execution of effects](#execution-of-effects)

Angular implicitly defines two implicit behaviors for its effects depending on the context they were created in.

A "View Effect" is an [`effect`](/api/core/effect) created in the context of a component instantiation. This includes effects created by services that are tied to component injectors.\
A "Root Effect" is created in the context of a root provided service instantiation.

The execution of both kinds of [`effect`](/api/core/effect) are tied to the change detection process.

* "View effects" are executed *before* their corresponding component is checked by the change detection process.
* "Root effects" are executed prior to all components being checked by the change detection process.

In both cases, if at least one of the effect dependencies changed during the effect execution, the effect will re-run before moving ahead on the change detection process.

## [Destroying effects](#destroying-effects)

When a component or directive is destroyed, Angular automatically cleans up any associated effects.

An [`effect`](/api/core/effect) can be created in two different contexts that will affect when it's destroyed:

* A "View effect" is destroyed when the component is destroyed.
* A "Root effect" is destroyed when the application is destroyed.

Effects return an [`EffectRef`](/api/core/EffectRef). You can use the ref's `destroy` method to manually dispose of an effect. You can combine this with the `manualCleanup` option when creating an effect to disable automatic cleanup. Be careful to actually destroy such effects when they're no longer required.

## [Effect cleanup functions](#effect-cleanup-functions)

When a component or directive is destroyed, Angular automatically cleans up any associated effects. Effects might start long-running operations, which you should cancel if the effect is destroyed or runs again before the first operation finished. When you create an effect, your function can optionally accept an `onCleanup` function as its first parameter. This `onCleanup` function lets you register a callback that is invoked before the next run of the effect begins, or when the effect is destroyed.

```
effect((onCleanup) => {
  const user = currentUser();

  const timer = setTimeout(() => {
    console.log(`1 second ago, the user became ${user}`);
  }, 1000);

  onCleanup(() => {
    clearTimeout(timer);
  });
});
```

## [Side effects on DOM elements](#side-effects-on-dom-elements)

The [`effect`](/api/core/effect) function is a general-purpose tool for running code in reaction to signal changes. However, it runs *before* the Angular updates the DOM. In some situations, you may need to manually inspect or modify the DOM, or integrate a 3rd-party library that requires direct DOM access.

For these situations, you can use [`afterRenderEffect`](/api/core/afterRenderEffect). It functions like [`effect`](/api/core/effect), but runs after Angular has finished rendering and committed its changes to the DOM.

```
@Component(/* ... */)
export class MyFancyChart {
  chartData = input.required<ChartData>();
  canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
  chart: ChartInstance;

  constructor() {
    // Run a single time to create the chart instance
    afterNextRender({
      write: () => {
        this.chart = initializeChart(this.canvas().nativeElement, this.chartData());
      },
    });

    // Re-run after DOM has been updated whenever `chartData` changes
    afterRenderEffect(() => {
      this.chart.updateData(this.chartData());
    });
  }
}
```

In this example [`afterRenderEffect`](/api/core/afterRenderEffect) is used to update a chart created by a 3rd party library.

**TIP:** You often don't need [`afterRenderEffect`](/api/core/afterRenderEffect) to check for DOM changes. APIs like `ResizeObserver`, `MutationObserver` and `IntersectionObserver` are preferred to [`effect`](/api/core/effect) or [`afterRenderEffect`](/api/core/afterRenderEffect) when possible.

### [Render phases](#render-phases)

Accessing the DOM and mutating it can impact the performance of your application, for example by triggering too many unnecessary [reflows](https://developer.mozilla.org/en-US/docs/Glossary/Reflow).

To optimize those operations, [`afterRenderEffect`](/api/core/afterRenderEffect) offers four phases to group the callbacks and execute them in an optimized order.

The phases are:

| Phase            | Description                                                                                                                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `earlyRead`      | Use this phase to read from the DOM before a subsequent write callback, for example to perform custom layout that the browser doesn't natively support. Prefer the read phase if reading can wait. |
| `write`          | Use this phase to write to the DOM. **Never** read from the DOM in this phase.                                                                                                                     |
| `mixedReadWrite` | Use this phase to read from and write to the DOM simultaneously. Never use this phase if it is possible to divide the work among the other phases instead.                                         |
| `read`           | Use this phase to read from the DOM. **Never** write to the DOM in this phase.                                                                                                                     |

Using these phases helps prevent layout thrashing and ensures that your DOM operations are performed in a safe and efficient manner.

You can specify the phases by passing an object with a callback for each phase to [`afterRenderEffect`](/api/core/afterRenderEffect):

```
afterRenderEffect({
  earlyRead: (cleanupFn) => {
    /* ... */
  },
  write: (previousPhaseValue, cleanupFn) => {
    /* ... */
  },
  mixedReadWrite: (previousPhaseValue, cleanupFn) => {
    /* ... */
  },
  read: (previousPhaseValue, cleanupFn) => {
    /* ... */
  },
});
```

**CRITICAL:** If you don't specify the phase, [`afterRenderEffect`](/api/core/afterRenderEffect) runs callbacks during the `mixedReadWrite` phase. This may worsen application performance by causing additional DOM reflows.

#### [Phase executions](#phase-executions)

The `earlyRead` phase callback receives only the cleanup function. Each subsequent phase receives the return value of the previous phase's callback as a Signal. You can use this to coordinate work across phases.

Effects run in the following phase order:

1. `earlyRead`
2. `write`
3. `mixedReadWrite`
4. `read`

If one of the phases modifies a signal value tracked by [`afterRenderEffect`](/api/core/afterRenderEffect), the affected phases execute again.

#### [Cleanup](#cleanup)

Each phase provides a cleanup callback function as argument. The cleanup callbacks are executed when the [`afterRenderEffect`](/api/core/afterRenderEffect) is destroyed or before re-running phase effects.

### [Server-side rendering caveats](#server-side-rendering-caveats)

[`afterRenderEffect`](/api/core/afterRenderEffect), similarly to [`afterNextRender`](/api/core/afterNextRender)/[`afterEveryRender`](/api/core/afterEveryRender), only runs on the client.

**NOTE:** Components are not guaranteed to be [hydrated](/guide/hydration) before the callback runs. You must use caution when directly reading or writing the DOM and layout.

----
url: https://angular.dev/cli/test
----

```
ng test [project] [options]
```

```
ng t [project] [options]
```

Takes the name of the project, as specified in the `projects` section of the `angular.json` workspace configuration file. When a project name is not supplied, it will execute for all projects.

## [Arguments](#arguments)

`project`

The name of the project to build. Can be an application or a library.

Value Type`string`

## [Options](#options)

`browser-viewport`

Specifies the browser viewport dimensions for browser-based tests in the format `widthxheight`.

Value Type`string`

`browsers`

Specifies the browsers to use for test execution. When not specified, tests are run in a Node.js environment using jsdom. For both Vitest and Karma, browser names ending with 'Headless' (e.g., 'ChromeHeadless') will enable headless mode.

Value Type`array`

`build-target`

Specifies the build target to use for the unit test build in the format `project:target[:configuration]`. This defaults to the `build` target of the current project with the `development` configuration. You can also pass a comma-separated list of configurations. Example: `project:target:production,staging`.

Value Type`string`

`configuration`

Alias`c`

One or more named builder configurations as a comma-separated list as specified in the "configurations" section in angular.json. The builder uses the named configurations to run the given target. For more information, see [https://angular.dev/reference/configs/workspace-config#alternate-build-configurations](/reference/configs/workspace-config#alternate-build-configurations).

Value Type`string`

`coverage`

Enables coverage reporting for tests. If not specified, the coverage configuration from a runner configuration file will be used if present. Otherwise, coverage is disabled by default.

Value Type`boolean`

`coverage-exclude`

Specifies glob patterns of files to exclude from the coverage report.

Value Type`array`

`coverage-include`

Specifies glob patterns of files to include in the coverage report.

Value Type`array`

`coverage-reporters`

Specifies the reporters to use for coverage results. Each reporter can be a string representing its name, or a tuple containing the name and an options object. Built-in reporters include 'html', 'lcov', 'lcovonly', 'text', 'text-summary', 'cobertura', 'json', and 'json-summary'.

Value Type`array`Allowed Values`cobertura`, `html`, `json`, `json-summary`, `lcov`, `lcovonly`, `text`, `text-summary`

`debug`

Enables debugging mode for tests, allowing the use of the Node Inspector.

Value Type`boolean`Default`false`

`exclude`

Specifies glob patterns of files to exclude from testing, relative to the project source root.

Value Type`array`

`filter`

Specifies a regular expression pattern to match against test suite and test names. Only tests with a name matching the pattern will be executed. For example, `^App` will run only tests in suites beginning with 'App'.

Value Type`string`

`headless`

Forces all configured browsers to run in headless mode. When using the Vitest runner, this option is ignored if no browsers are configured. The Karma runner does not support this option.

Value Type`boolean`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`include`

Specifies glob patterns of files to include for testing, relative to the project source root. This option also has special handling for directory paths (includes all test files within) and file paths (includes the corresponding test file if one exists).

Value Type`array`Default`**/*.spec.ts,**/*.test.ts`

`isolate`

Enables isolation for test execution. When true, Vitest runs tests in separate threads or processes. This option is only available for the Vitest runner. Defaults to false to align with the Karma/Jasmine experience.

Value Type`boolean`

`list-tests`

Lists all discovered test files and exits the process without building or executing the tests.

Value Type`boolean`Default`false`

`output-file`

Specifies a file path for the test report, applying only to the first reporter. To configure output files for multiple reporters, use the tuple format `['reporter-name', { outputFile: '...' }]` within the `reporters` option. When not provided, output is written to the console.

Value Type`string`

`progress`

Shows build progress information in the console. Defaults to the `progress` setting of the specified `buildTarget`.

Value Type`boolean`

`providers-file`

Specifies the path to a TypeScript file that provides an array of Angular providers for the test environment. The file must contain a default export of the provider array.

Value Type`string`

`quiet`

Suppresses the verbose build summary and stats table on each rebuild. Defaults to `true` locally and `false` in CI environments.

Value Type`boolean`

`reporters`

Specifies the reporters to use during test execution. Each reporter can be a string representing its name, or a tuple containing the name and an options object. Built-in reporters include 'default', 'verbose', 'dots', 'json', 'junit', 'tap', 'tap-flat', and 'html'. You can also provide a path to a custom reporter.

Value Type`array`

`runner`

Specifies the test runner to use for test execution.

Value Type`string`Allowed Values`karma`, `vitest`Default`vitest`

`runner-config`

Specifies the configuration file for the selected test runner. If a string is provided, it will be used as the path to the configuration file. If `true`, the builder will search for a default configuration file (e.g., `vitest-base.config.mts` or `karma.conf.js`). If `false`, no external configuration file will be used.\nFor Vitest, this enables advanced options and the use of custom plugins. Please note that while the file is loaded, the Angular team does not provide direct support for its specific contents or any third-party plugins used within it.

Value Type`string`

`setup-files`

A list of paths to global setup files that are executed before the test files. The application's polyfills and the Angular TestBed are always initialized before these files.

Value Type`array`

`splitting`

Enables code splitting for test execution. When enabled, shared code between test files is split into separate chunks. Issues with live ESM bindings in Node.js environments (such as uninitialized exports or mocking failures) can be resolved by disabling splitting. This option is only available for the Vitest runner.

Value Type`boolean`Default`true`

`ts-config`

The path to the TypeScript configuration file, relative to the workspace root. Defaults to `tsconfig.spec.json` in the project root if it exists. If not specified and the default does not exist, the `tsConfig` from the specified `buildTarget` will be used.

Value Type`string`

`ui`

Enables the Vitest UI for interactive test execution. This option is only available for the Vitest runner.

Value Type`boolean`

`watch`

Enables watch mode, which re-runs tests when source files change. Defaults to `true` in TTY environments and `false` otherwise.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/api/router/ROUTER_INITIALIZER
----

```
    
      const ROUTER_INITIALIZER: InjectionToken<(compRef: ComponentRef<any>) => void>;
    
    
```

Jump to details

----
url: https://angular.dev/api/ssr/node/NodeRequestHandlerFunction
----

* * [AngularNodeAppEngine](/api/ssr/node/AngularNodeAppEngine)
  * [AngularNodeAppEngineOptions](/api/ssr/node/AngularNodeAppEngineOptions)
  * [CommonEngine](/api/ssr/node/CommonEngine)
  * [CommonEngineOptions](/api/ssr/node/CommonEngineOptions)
  * [CommonEngineRenderOptions](/api/ssr/node/CommonEngineRenderOptions)
  * [createNodeRequestHandler](/api/ssr/node/createNodeRequestHandler)
  * [createWebRequestFromNodeRequest](/api/ssr/node/createWebRequestFromNodeRequest)
  * [isMainModule](/api/ssr/node/isMainModule)
  * [NodeRequestHandlerFunction](/api/ssr/node/NodeRequestHandlerFunction)
  * [writeResponseToNodeResponse](/api/ssr/node/writeResponseToNodeResponse)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](/api/ssr/node/NodeRequestHandlerFunction#api)

```
    
      type NodeRequestHandlerFunction = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => Promise<void> | void
    
    
```

Jump to details

----
url: https://angular.dev/api/common/I18nSelectPipe
----

```
{{ value_expression | i18nSelect:mapping }}
```

## [API](#api)

```
    
      class I18nSelectPipe implements PipeTransform {}
    
    
```

@paramvalue`string | null | undefined`

a string to be internationalized.

@parammapping`{ [key: string]: string; }`

an object that indicates the text that should be displayed for different values of the provided `value`.

@returns`string`

## [Description](#description)

Generic selector that displays the string that matches the current value.

If none of the keys of the `mapping` match the `value`, then the content of the `other` key is returned when present, otherwise an empty string is returned.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

### [Example](#example)

```
@Component({
  selector: 'i18n-select-pipe',
  imports: [I18nSelectPipe],
  template: `<div>{{ gender | i18nSelect: inviteMap }}</div>`,
})
export class I18nSelectPipeComponent {
  gender: string = 'male';
  inviteMap: any = {'male': 'Invite him.', 'female': 'Invite her.', 'other': 'Invite them.'};
}
```

Jump to details

----
url: https://angular.dev/api/core/BootstrapOptions
----

Configure [`NgZone`](/api/core/NgZone) in the `providers` array of the application module instead.

## [API](/api/core/BootstrapOptions#api)

```
    
      interface BootstrapOptions {}
    
    
```

@deprecated

BootstrapOptions is deprecated. Provide [`NgZone`](/api/core/NgZone) in the `providers` array of the module instead.

Optionally specify which [`NgZone`](/api/core/NgZone) should be used when not configured in the providers.

* Provide your own [`NgZone`](/api/core/NgZone) instance.
* `zone.js` - Use default [`NgZone`](/api/core/NgZone) which requires `Zone.js`.
* `noop` - Use `NoopNgZone` which does nothing.

@deprecated

BootstrapOptions is deprecated. Use [`provideZoneChangeDetection`](/api/core/provideZoneChangeDetection) instead to configure coalescing.

Optionally specify coalescing event change detections or not. Consider the following case.

```
<div (click)="doSomething()">
  <button (click)="doSomethingElse()"></button>
</div>
```

When button is clicked, because of the event bubbling, both event handlers will be called and 2 change detections will be triggered. We can coalesce such kind of events to only trigger change detection only once.

By default, this option will be false. So the events will not be coalesced and the change detection will be triggered multiple times. And if this option be set to true, the change detection will be triggered async by scheduling a animation frame. So in the case above, the change detection will only be triggered once.

@deprecated

BootstrapOptions is deprecated. Use [`provideZoneChangeDetection`](/api/core/provideZoneChangeDetection) instead to configure coalescing.

Optionally specify if [`NgZone#run()`](/api/core/NgZone#run) method invocations should be coalesced into a single change detection.

Consider the following case.

```
for (let i = 0; i < 10; i ++) {
  ngZone.run(() => {
    // do something
  });
}
```

This case triggers the change detection multiple times. With ngZoneRunCoalescing options, all change detections in an event loop trigger only once. In addition, the change detection executes in requestAnimation.

Jump to details

----
url: https://angular.dev/api/forms/ValidationErrors
----

```
    
      type ValidationErrors = {  [key: string]: any;}
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CDK_DRAG_PREVIEW
----

```
    
      const CDK_DRAG_PREVIEW: any;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/testing/TestEnvironmentOptions
----

```
    
      interface TestEnvironmentOptions {}
    
    
```

Configures the test module teardown behavior in [`TestBed`](/api/core/testing/TestBed).

Whether errors should be thrown when unknown elements are present in component's template. Defaults to `false`, where the error is simply logged. If set to `true`, the error is thrown.

Whether errors should be thrown when unknown properties are present in component's template. Defaults to `false`, where the error is simply logged. If set to `true`, the error is thrown.

Jump to details

----
url: https://angular.dev/errors/NG0207
----

This error occurs when [`EnvironmentProviders`](/api/core/EnvironmentProviders) are used in a context that only accepts regular providers, such as a component's `providers` array. Environment providers are designed for application-wide configuration and can only be used in environment injectors (like the root injector configured in [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) or route configurations).

## [Common scenarios](#common-scenarios)

### [Using `provideHttpClient()` in component providers](#using-providehttpclient-in-component-providers)

Functions like [`provideHttpClient()`](/api/common/http/provideHttpClient) return [`EnvironmentProviders`](/api/core/EnvironmentProviders), which cannot be used at the component level:

```
@Component({
  providers: [
    provideHttpClient(), // ERROR: EnvironmentProviders can't be used here
  ],
})
export class UserProfile {}
```

### [Using `importProvidersFrom()` in component providers](#using-importprovidersfrom-in-component-providers)

The [`importProvidersFrom()`](/api/core/importProvidersFrom) function also returns [`EnvironmentProviders`](/api/core/EnvironmentProviders):

```
@Component({
  providers: [
    importProvidersFrom(SomeModule), // ERROR: can't be used for component providers
  ],
})
export class DataView {}
```

## [Debugging the error](#debugging-the-error)

Move the environment providers to an appropriate location:

### [For application-wide providers](#for-application-wide-providers)

Configure environment providers in [`bootstrapApplication`](/api/platform-browser/bootstrapApplication):

```
bootstrapApplication(App, {
  providers: [provideHttpClient(), importProvidersFrom(SomeModule)],
});
```

### [For route-specific providers](#for-route-specific-providers)

Use the `providers` array in route configurations:

```
const routes: Routes = [
  {
    path: 'admin',
    component: AdminView,
    providers: [provideHttpClient(withInterceptors([authInterceptor]))],
  },
];
```

### [For component-level services](#for-component-level-services)

If you need component-scoped services, use regular providers instead of environment providers:

```
@Component({
  providers: [
    UserClient, // Regular provider - this works
    {provide: API_URL, useValue: '/api'}, // Value provider - this works
  ],
})
export class UserProfile {}
```

The error message specifies which provider caused the issue. Check that all items in your component's `providers` array are regular providers, not environment providers returned by functions like [`provideHttpClient()`](/api/common/http/provideHttpClient), [`provideRouter()`](/api/router/provideRouter), or [`importProvidersFrom()`](/api/core/importProvidersFrom).

----
url: https://angular.dev/guide/tailwind
----

[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework that can be used to build modern websites without ever leaving your HTML. This guide will walk you through setting up Tailwind CSS in your Angular project.

## [Automated Setup with `ng add`](#automated-setup-with-ng-add)

Angular CLI provides a streamlined way to integrate Tailwind CSS into your project using the `ng add` command. This command automatically installs the necessary packages, configures Tailwind CSS, and updates your project's build settings.

First, navigate to your Angular project's root directory in a terminal and run the following command:

```
ng add tailwindcss
```

This command performs the following actions:

* Installs `tailwindcss` and its peer dependencies.
* Configures the project to use Tailwind CSS.
* Adds the Tailwind CSS `@import` statement to your styles.

After running `ng add tailwindcss`, you can immediately start using Tailwind's utility classes in your component templates.

## [Manual Setup (Alternative Method)](#manual-setup-alternative-method)

If you prefer to set up Tailwind CSS manually, follow these steps:

### [1. Create an Angular project](#1-create-an-angular-project)

First, create a new Angular project if you don't have one set up already.

```
ng new my-project
cd my-project
```

### [2. Install Tailwind CSS](#2-install-tailwind-css)

Next, open a terminal in your Angular project's root directory and run the following command to install Tailwind CSS and its peer dependencies:

### npm

```
npm install tailwindcss @tailwindcss/postcss postcss
```

### yarn

```
yarn add tailwindcss @tailwindcss/postcss postcss
```

### pnpm

```
pnpm add tailwindcss @tailwindcss/postcss postcss
```

### bun

```
bun add tailwindcss @tailwindcss/postcss postcss
```

### [3. Configure PostCSS Plugins](#3-configure-postcss-plugins)

Next, add a `.postcssrc.json` file in the file root of the project. Add the `@tailwindcss/postcss` plugin into your PostCSS configuration.

### .postcssrc.json

```
{
  "plugins": {
    "@tailwindcss/postcss": {}
  }
}
```

### [4. Import Tailwind CSS](#4-import-tailwind-css)

Add an `@import` to `./src/styles.css` that imports Tailwind CSS.

### src/styles.css

```
@import 'tailwindcss';
```

If you're using SCSS, add `@use` to `./src/styles.scss`.

### src/styles.scss

```
@use 'tailwindcss';
```

### [5. Start using Tailwind in your project](#5-start-using-tailwind-in-your-project)

You can now start using Tailwind's utility classes in your component templates to style your application. Run your build process with `ng serve` and you should see the styled heading.

For example, you can add the following to your `app.html` file:

```
<h1 class="text-3xl font-bold underline">Hello world!</h1>
```

## [Additional Resources](#additional-resources)

* [Tailwind CSS Documentation](https://tailwindcss.com/docs)

----
url: https://angular.dev/api/core/NO_ERRORS_SCHEMA
----

```
    
      const NO_ERRORS_SCHEMA: SchemaMetadata;
    
    
```

## [Description](#description)

Defines a schema that allows any property on any element.

This schema allows you to ignore the errors related to any unknown elements or properties in a template. The usage of this schema is generally discouraged because it prevents useful validation and may hide real errors in your template. Consider using the [`CUSTOM_ELEMENTS_SCHEMA`](/api/core/CUSTOM_ELEMENTS_SCHEMA) instead.

Jump to details

----
url: https://angular.dev/api/core/OnDestroy
----

```
    
      interface OnDestroy {}
    
    
```

A callback method that performs custom clean-up, invoked immediately before a directive, pipe, or service instance is destroyed.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define its own custom clean-up method.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements OnDestroy {
        ngOnDestroy() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/api/forms/isFormControl
----

```
    
      const isFormControl: (control: unknown) => control is FormControl<any>;
    
    
```

Jump to details

----
url: https://angular.dev/guide/testing/components-basics
----

A component, unlike all other parts of an Angular application, combines an HTML template and a TypeScript class. The component truly is the template and the class *working together*. To adequately test a component, you should test that they work together as intended.

Such tests require creating the component's host element in the browser DOM, as Angular does, and investigating the component class's interaction with the DOM as described by its template.

The Angular [`TestBed`](/api/core/testing/TestBed) facilitates this kind of testing as you'll see in the following sections. But in many cases, *testing the component class alone*, without DOM involvement, can validate much of the component's behavior in a straightforward, more obvious way.

## [Component DOM testing](#component-dom-testing)

A component is more than just its class. A component interacts with the DOM and with other components. Classes alone cannot tell you if the component is going to render properly, respond to user input and gestures, or integrate with its parent and child components.

* Is `Lightswitch.clicked()` bound to anything such that the user can invoke it?
* Is the `Lightswitch.message` displayed?
* Can the user actually select the hero displayed by the `DashboardHero` component?
* Is the hero name displayed as expected (such as uppercase)?
* Is the welcome message displayed by the template of the `Welcome` component?

These might not be troubling questions for the preceding simple components illustrated. But many components have complex interactions with the DOM elements described in their templates, causing HTML to appear and disappear as the component state changes.

To answer these kinds of questions, you have to create the DOM elements associated with the components, you must examine the DOM to confirm that component state displays properly at the appropriate times, and you must simulate user interaction with the screen to determine whether those interactions cause the component to behave as expected.

To write these kinds of test, you'll use additional features of the [`TestBed`](/api/core/testing/TestBed) as well as other testing helpers.

### [CLI-generated tests](#cli-generated-tests)

The CLI creates an initial test file for you by default when you ask it to generate a new component.

For example, the following CLI command generates a `Banner` component in the `app/banner` folder (with inline template and styles):

```
ng generate component banner --inline-template --inline-style
```

It also generates an initial test file for the component, `banner.spec.ts`, that looks like this:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Banner} from './banner';

describe('Banner', () => {
  let component: Banner;
  let fixture: ComponentFixture<Banner>;

  beforeEach(async () => {
    TestBed.configureTestingModule({});

    fixture = TestBed.createComponent(Banner);
    component = fixture.componentInstance;
    await fixture.whenStable();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
```

### [Reduce the setup](#reduce-the-setup)

Only the last three lines of this file actually test the component and all they do is assert that Angular can create the component.

The rest of the file is boilerplate setup code anticipating more advanced tests that *might* become necessary if the component evolves into something substantial.

You'll learn about these advanced test features in the following sections. For now, you can radically reduce this test file to a more manageable size:

```
describe('Banner (minimal)', () => {
  it('should create', () => {
    const fixture = TestBed.createComponent(Banner);
    const component = fixture.componentInstance;
    expect(component).toBeDefined();
  });
});
```

Later you'll call [`TestBed.configureTestingModule()`](/api/core/testing/TestBed#configureTestingModule) with imports, providers, and more declarations to suit your testing needs. Optional `override` methods can further fine-tune aspects of the configuration.

**NOTE:** [`TestBed.compileComponents`](/api/core/testing/TestBed#compileComponents) is only required when `@defer` blocks are used in the tested components.

### [`createComponent()`](#createcomponent)

After configuring [`TestBed`](/api/core/testing/TestBed), you call its [`createComponent()`](/api/core/createComponent) method.

```
const fixture = TestBed.createComponent(Banner);
```

[`TestBed.createComponent()`](/api/core/testing/TestBed#createComponent) creates an instance of the `Banner` component, adds a corresponding element to the test-runner DOM, and returns a [`ComponentFixture`](#componentfixture).

**IMPORTANT:** Do not re-configure [`TestBed`](/api/core/testing/TestBed) after calling [`createComponent`](/api/core/createComponent).

The [`createComponent`](/api/core/createComponent) method freezes the current [`TestBed`](/api/core/testing/TestBed) definition, closing it to further configuration.

You cannot call any more [`TestBed`](/api/core/testing/TestBed) configuration methods, not `configureTestingModule()`, nor any of the `override...` methods. If you try, [`TestBed`](/api/core/testing/TestBed) throws an error.

### [`ComponentFixture`](#componentfixture)

The [`ComponentFixture`](api/core/testing/ComponentFixture) is a test harness for interacting with the created component and its corresponding element.

Access the component instance through the fixture and confirm it exists with an expectation:

```
const component = fixture.componentInstance;
expect(component).toBeDefined();
```

### [`beforeEach()`](#beforeeach)

You will add more tests as this component evolves. Rather than duplicate the [`TestBed`](/api/core/testing/TestBed) configuration for each test, you refactor to pull the setup into a `beforeEach()` and some supporting variables:

```
describe('Banner (with beforeEach)', () => {
  let component: Banner;
  let fixture: ComponentFixture<Banner>;

  beforeEach(async () => {
    fixture = TestBed.createComponent(Banner);
    component = fixture.componentInstance;

    await fixture.whenStable(); // necessary to wait for the initial rendering
  });

  it('should create', () => {
    expect(component).toBeDefined();
  });
});
```

**HELPFUL:** By awaiting the initial rendering in the `beforeEach` with `await fixture.whenStable` the single tests synchronous.

Now add a test that gets the component's element from `fixture.nativeElement` and looks for the expected text.

```
it('should contain "banner works!"', () => {
  const bannerElement: HTMLElement = fixture.nativeElement;
  expect(bannerElement.textContent).toContain('banner works!');
});
```

### [create a `setup` function](#create-a-setup-function)

As an alternative to `beforeEach`, you can also create a setup function that you will call in every test. A setup function has the advantage of being customizable via parameters.

Here is an example of what a setup function could look like:

```
function setup(providers?: Provider[]): ComponentFixture<Banner> {
  TestBed.configureTestingModule({providers});
  return TestBed.createComponent(Banner);
}
```

### [`nativeElement`](#nativeelement)

The value of [`ComponentFixture.nativeElement`](/api/core/testing/ComponentFixture#nativeElement) has the `any` type. Later you'll encounter the [`DebugElement.nativeElement`](/api/core/DebugElement#nativeElement) and it too has the `any` type.

Angular can't know at compile time what kind of HTML element the `nativeElement` is or if it even is an HTML element. The application might be running on a *non-browser platform*, such as the server or a node environment, where the element might have a diminished API or not exist at all.

The tests in this guide are designed to run in a browser so a `nativeElement` value will always be an `HTMLElement` or one of its derived classes.

Knowing that it is an `HTMLElement` of some sort, use the standard HTML `querySelector` to dive deeper into the element tree.

Here's another test that calls `HTMLElement.querySelector` to get the paragraph element and look for the banner text:

```
it('should have <p> with "banner works!"', () => {
  const bannerElement: HTMLElement = fixture.nativeElement;
  const p = bannerElement.querySelector('p')!;
  expect(p.textContent).toEqual('banner works!');
});
```

### [`DebugElement`](#debugelement)

The Angular *fixture* provides the component's element directly through the `fixture.nativeElement`.

```
const bannerElement: HTMLElement = fixture.nativeElement;
```

This is actually a convenience method, implemented as `fixture.debugElement.nativeElement`.

```
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
```

There's a good reason for this circuitous path to the element.

The properties of the `nativeElement` depend upon the runtime environment. You could be running these tests on a *non-browser* platform that doesn't have a DOM or whose DOM-emulation doesn't support the full `HTMLElement` API.

Angular relies on the [`DebugElement`](/api/core/DebugElement) abstraction to work safely across *all supported platforms*. Instead of creating an HTML element tree, Angular creates a [`DebugElement`](/api/core/DebugElement) tree that wraps the *native elements* for the runtime platform. The `nativeElement` property unwraps the [`DebugElement`](/api/core/DebugElement) and returns the platform-specific element object.

Because the sample tests for this guide are designed to run only in a browser, a `nativeElement` in these tests is always an `HTMLElement` whose familiar methods and properties you can explore within a test.

Here's the previous test, re-implemented with `fixture.debugElement.nativeElement`:

```
it('should find the <p> with fixture.debugElement.nativeElement', () => {
  const bannerDe: DebugElement = fixture.debugElement;
  const bannerEl: HTMLElement = bannerDe.nativeElement;
  const p = bannerEl.querySelector('p')!;
  expect(p.textContent).toEqual('banner works!');
});
```

The [`DebugElement`](/api/core/DebugElement) has other methods and properties that are useful in tests, as you'll see elsewhere in this guide.

You import the [`DebugElement`](/api/core/DebugElement) symbol from the Angular core library.

```
import {DebugElement} from '@angular/core';
```

### [`By.css()`](#bycss)

Although the tests in this guide all run in the browser, some applications might run on a different platform at least some of the time.

For example, the component might render first on the server as part of a strategy to make the application launch faster on poorly connected devices. The server-side renderer might not support the full HTML element API. If it doesn't support `querySelector`, the previous test could fail.

The [`DebugElement`](/api/core/DebugElement) offers query methods that work for all supported platforms. These query methods take a *predicate* function that returns `true` when a node in the [`DebugElement`](/api/core/DebugElement) tree matches the selection criteria.

You create a *predicate* with the help of a [`By`](/api/platform-browser/By) class imported from a library for the runtime platform. Here's the [`By`](/api/platform-browser/By) import for the browser platform:

```
import {By} from '@angular/platform-browser';
```

The following example re-implements the previous test with [`DebugElement.query()`](/api/core/DebugElement#query) and the browser's [`By.css`](/api/platform-browser/By#css) method.

```
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
  const bannerDe: DebugElement = fixture.debugElement;
  const paragraphDe = bannerDe.query(By.css('p'));
  const p: HTMLElement = paragraphDe.nativeElement;
  expect(p.textContent).toEqual('banner works!');
});
```

Some noteworthy observations:

* The [`By.css()`](/api/platform-browser/By#css) static method selects [`DebugElement`](/api/core/DebugElement) nodes with a [standard CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors "CSS selectors").
* The query returns a [`DebugElement`](/api/core/DebugElement) for the paragraph.
* You must unwrap that result to get the paragraph element.

When you're filtering by CSS selector and only testing properties of a browser's *native element*, the [`By.css`](/api/platform-browser/By#css) approach might be overkill.

It's often more straightforward and clear to filter with a standard `HTMLElement` method such as `querySelector()` or `querySelectorAll()`.

----
url: https://angular.dev/api/animations/transition
----

```
function transition(
  stateChangeExpr:
    | string
    | ((
        fromState: string,
        toState: string,
        element?: any,
        params?: { [key: string]: any } | undefined,
      ) => boolean),
  steps: AnimationMetadata | AnimationMetadata[],
  options?: AnimationOptions | null,
): AnimationTransitionMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramstateChangeExpr`string | ((fromState: string, toState: string, element?: any, params?: { [key: string]: any; } | undefined) => boolean)`

A string with a specific format or a function that specifies when the animation transition should occur (see [State Change Expression](#state-change-expression)).

@paramsteps`AnimationMetadata | AnimationMetadata[]`

One or more animation objects that represent the animation's instructions.

@paramoptions`AnimationOptions | null`

An options object that can be used to specify a delay for the animation or provide custom parameters for it.

@returns`AnimationTransitionMetadata`

An object that encapsulates the transition data.

## [Usage Notes](#usage-notes)

### [State Change Expression](#state-change-expression)

The State Change Expression instructs Angular when to run the transition's animations, it can either be

* a string with a specific syntax
* or a function that compares the previous and current state (value of the expression bound to the element's trigger) and returns `true` if the transition should occur or `false` otherwise

The string format can be:

* `fromState => toState`, which indicates that the transition's animations should occur then the expression bound to the trigger's element goes from `fromState` to `toState`

  *Example:*

  ```
  transition('open => closed', animate('.5s ease-out', style({ height: 0 }) ))
  ```

* `fromState <=> toState`, which indicates that the transition's animations should occur then the expression bound to the trigger's element goes from `fromState` to `toState` or vice versa

  *Example:*

  ```
  transition('enabled <=> disabled', animate('1s cubic-bezier(0.8,0.3,0,1)'))
  ```

* `:enter`/`:leave`, which indicates that the transition's animations should occur when the element enters or exists the DOM

  *Example:*

  ```
  transition(':enter', [
    style({ opacity: 0 }),
    animate('500ms', style({ opacity: 1 }))
  ])
  ```

* `:increment`/`:decrement`, which indicates that the transition's animations should occur when the numerical expression bound to the trigger's element has increased in value or decreased

  *Example:*

  ```
  transition(':increment', query('@counter', animateChild()))
  ```

* a sequence of any of the above divided by commas, which indicates that transition's animations should occur whenever one of the state change expressions matches

  *Example:*

  ```
  transition(':increment, * => enabled, :enter', animate('1s ease', keyframes([
    style({ transform: 'scale(1)', offset: 0}),
    style({ transform: 'scale(1.1)', offset: 0.7}),
    style({ transform: 'scale(1)', offset: 1})
  ]))),
  ```

Also note that in such context:

* `void` can be used to indicate the absence of the element
* asterisks can be used as wildcards that match any state
* (as a consequence of the above, `void => *` is equivalent to `:enter` and `* => void` is equivalent to `:leave`)
* `true` and `false` also match expression values of `1` and `0` respectively (but do not match *truthy* and *falsy* values)

Be careful about entering end leaving elements as their transitions present a common pitfall for developers.

Note that when an element with a trigger enters the DOM its `:enter` transition always gets executed, but its `:leave` transition will not be executed if the element is removed alongside its parent (as it will be removed "without warning" before its transition has a chance to be executed, the only way that such transition can occur is if the element is exiting the DOM on its own).

### [Animating to a Final State](#animating-to-a-final-state)

If the final step in a transition is a call to [`animate()`](/api/animations/animate) that uses a timing value with no `style` data, that step is automatically considered the final animation arc, for the element to reach the final state, in such case Angular automatically adds or removes CSS styles to ensure that the element is in the correct final state.

### [Usage Examples](#usage-examples)

* Transition animations applied based on the trigger's expression value

```
<div [@myAnimationTrigger]="myStatusExp">
 ...
</div>
```

```
trigger("myAnimationTrigger", [
  ..., // states
  transition("on => off, open => closed", animate(500)),
  transition("* <=> error", query('.indicator', animateChild()))
])
```

* Transition animations applied based on custom logic dependent on the trigger's expression value and provided parameters

  ```
  <div [@myAnimationTrigger]="{
   value: stepName,
   params: { target: currentTarget }
  }">
   ...
  </div>
  ```

  ```
  trigger("myAnimationTrigger", [
    ..., // states
    transition(
      (fromState, toState, _element, params) =>
        ['firststep', 'laststep'].includes(fromState.toLowerCase())
        && toState === params?.['target'],
      animate('1s')
    )
  ])
  ```

Jump to details

----
url: https://angular.dev/cli/deploy
----

```
ng deploy [project] [options]
```

The command takes an optional project name, as specified in the `projects` section of the `angular.json` workspace configuration file. When a project name is not supplied, executes the `deploy` builder for the default project.

To use the `ng deploy` command, use `ng add` to add a package that implements deployment capabilities to your favorite platform. Adding the package automatically updates your workspace configuration, adding a deployment [CLI builder](tools/cli/cli-builder). For example:

```
"projects": {
  "my-project": {
    ...
    "architect": {
      ...
      "deploy": {
        "builder": "@angular/fire:deploy",
        "options": {}
      }
    }
  }
}
```

----
url: https://angular.dev/api/common/formatCurrency
----

```
function formatCurrency(
  value: number,
  locale: string,
  currency: string,
  currencyCode?: string | undefined,
  digitsInfo?: string | undefined,
): string;
```

@paramvalue`number`

The number to format.

@paramlocale`string`

A locale code for the locale format rules to use.

@paramcurrency`string`

A string containing the currency symbol or its name, such as "$" or "Canadian Dollar". Used in output string, but does not affect the operation of the function.

@paramcurrencyCode`string | undefined`

The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such as `USD` for the US dollar and `EUR` for the euro. Used to determine the number of digits in the decimal part.

@paramdigitsInfo`string | undefined`

Decimal representation options, specified by a string in the following format: `{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}`. See [`DecimalPipe`](/api/common/DecimalPipe) for more details.

@returns`string`

The formatted currency value.

## [Description](#description)

Formats a number as currency using locale rules.

## [Exported by](#exported-by)

* `CommonModule`

Jump to details

----
url: https://angular.dev/api/common/http/withInterceptorsFromDi
----

```
function withInterceptorsFromDi(): HttpFeature<HttpFeatureKind.LegacyInterceptors>;
```

@returns`HttpFeature<HttpFeatureKind.LegacyInterceptors>`

## [Description](#description)

Includes class-based interceptors configured using a multi-provider in the current injector into the configured [`HttpClient`](/api/common/http/HttpClient) instance.

Prefer [`withInterceptors`](/api/common/http/withInterceptors) and functional interceptors instead, as support for DI-provided interceptors may be phased out in a later release.

Jump to details

----
url: https://angular.dev/api/forms/signals/minDateError
----

```
function minDateError(
  minDate: Date,
  options: WithFieldTree<ValidationErrorOptions>,
): MinDateValidationError;
function minDateError(
  minDate: Date,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<MinDateValidationError>;
```

Create a minDate error associated with the target field

@paramminDate`Date`

The min date constraint

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`MinDateValidationError`

Create a minDate error

@paramminDate`Date`

The min date constraint

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<MinDateValidationError>`

Jump to details

----
url: https://angular.dev/guide/components/host-elements
----

Angular creates an instance of a component for every HTML element that matches the component's selector. The DOM element that matches a component's selector is that component's **host element**. The contents of a component's template are rendered inside its host element.

```
// Component source
@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
})
export class ProfilePhoto {}
```

```
<!-- Using the component -->
<h3>Your profile photo</h3>
<profile-photo />
<button>Upload a new profile photo</button>
```

```
<!-- Rendered DOM -->
<h3>Your profile photo</h3>
<profile-photo>
  <img src="profile-photo.jpg" alt="Your profile photo" />
</profile-photo>
<button>Upload a new profile photo</button>
```

In the above example, `<profile-photo>` is the host element of the `ProfilePhoto` component.

## [Binding to the host element](#binding-to-the-host-element)

A component can bind properties, attributes, styles and events to its host element. This behaves identically to bindings on elements inside the component's template, but instead defined with the `host` property in the [`@Component`](/api/core/Component) decorator:

```
@Component({
  ...,
  host: {
    'role': 'slider',
    '[attr.aria-valuenow]': 'value',
    '[class.active]': 'isActive()',
    '[style.background]' : `hasError() ? 'red' : 'green'`,
    '[tabIndex]': 'disabled ? -1 : 0',
    '(keydown)': 'updateValue($event)',
  },
})
export class CustomSlider {
  value: number = 0;
  disabled: boolean = false;
  isActive = signal(false);
  hasError = signal(false);
  updateValue(event: KeyboardEvent) { /* ... */ }

  /* ... */
}
```

**NOTE:** The global target names that can be used to prefix an event name are `document:`, `window:` and `body:`.

**NOTE:** Key names like `'(keydown.enter)'` are matched against `KeyboardEvent.key`, which depends on the user's keyboard layout and input language. To match a physical key regardless of layout, use the `code` modifier instead, e.g. `'(keydown.code.Enter)'`. See [Using key modifiers](guide/templates/event-listeners#using-key-modifiers) for details.

## [The `@HostBinding` and `@HostListener` decorators](#the-hostbinding-and-hostlistener-decorators)

You can alternatively bind to the host element by applying the [`@HostBinding`](/api/core/HostBinding) and [`@HostListener`](/api/core/HostListener) decorator to class members.

[`@HostBinding`](/api/core/HostBinding) lets you bind host properties and attributes to properties and getters:

```
@Component({/* ... */})
export class CustomSlider {
  @HostBinding('attr.aria-valuenow')
  value: number = 0;

  @HostBinding('tabIndex')
  get tabIndex() {
    return this.disabled ? -1 : 0;
  }

  /* ... */
}
```

[`@HostListener`](/api/core/HostListener) lets you bind event listeners to the host element. The decorator accepts an event name and an optional array of arguments:

```
export class CustomSlider {
  @HostListener('keydown', ['$event'])
  updateValue(event: KeyboardEvent) {
    /* ... */
  }
}
```

### Prefer using the `host` property over the decorators

**Always prefer using the `host` property over [`@HostBinding`](/api/core/HostBinding) and [`@HostListener`](/api/core/HostListener).** These decorators exist exclusively for backwards compatibility.

## [Binding collisions](#binding-collisions)

When you use a component in a template, you can add bindings to that component instance's element. The component may *also* define host bindings for the same properties or attributes.

```
@Component({
  ...,
  host: {
    'role': 'presentation',
    '[id]': 'id',
  }
})
export class ProfilePhoto { /* ... */ }
```

```
<profile-photo role="group" [id]="otherId" />
```

In cases like this, the following rules determine which value wins:

* If both values are static, the instance binding wins.
* If one value is static and the other dynamic, the dynamic value wins.
* If both values are dynamic, the component's host binding wins.

## [Styling with CSS custom properties](#styling-with-css-custom-properties)

Developers often rely on [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascading_variables/Using_CSS_custom_properties) to enable a flexible configuration of their component's styles. You can set such custom properties on a host element with a [style binding](guide/templates/binding#css-style-properties).

```
@Component({
  /* ... */
  host: {
    '[style.--my-background]': 'color()',
  },
})
export class MyComponent {
  color = signal('lightgreen');
}
```

In this example, the `--my-background` CSS custom property is bound to the `color` signal. The value of the custom property will automatically update whenever the `color` signal changes. This will affect the current component and all its children that rely on this custom property.

### [Setting custom properties on children components](#setting-custom-properties-on-children-components)

Alternatively, it is also possible to set css custom properties on the host element of children components with a [style binding](guide/templates/binding#css-style-properties).

```
@Component({
  selector: 'my-component',
  template: `<my-child [style.--my-background]="color()" />`,
})
export class MyComponent {
  color = signal('lightgreen');
}
```

## [Injecting host element attributes](#injecting-host-element-attributes)

Components and directives can read static attributes from their host element by using [`HostAttributeToken`](/api/core/HostAttributeToken) together with the [`inject`](api/core/inject) function.

```
import { Component, HostAttributeToken, inject } from '@angular/core';

@Component({
  selector: 'app-button',
  ...,
})
export class Button {
  variation = inject(new HostAttributeToken('variation'));
}
```

```
<app-button variation="primary">Click me</app-button>
```

**HELPFUL:** [`HostAttributeToken`](/api/core/HostAttributeToken) throws an error if the attribute is missing, unless the injection is marked as optional.

----
url: https://angular.dev/api/core/CreateComputedOptions
----

```
    
      interface CreateComputedOptions<T> {}
    
    
```

A comparison function which defines equality for computed values.

A debug name for the computed signal. Used in Angular DevTools to identify the signal.

Jump to details

----
url: https://angular.dev/api/elements/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/guide/templates/control-flow
----

Angular templates support control flow blocks that let you conditionally show, hide, and repeat elements.

## [Conditionally display content with `@if`, `@else if` and `@else`](#conditionally-display-content-with-if-else-if-and-else)

The `@if` block conditionally displays its content when its condition expression is truthy:

```
@if (a > b) {
  <p>{{ a }} is greater than {{ b }}</p>
}
```

If you want to display alternative content, you can do so by providing any number of `@else if` blocks and a singular `@else` block.

```
@if (a > b) {
  {{ a }} is greater than {{ b }}
} @else if (b > a) {
  {{ a }} is less than {{ b }}
} @else {
  {{ a }} is equal to {{ b }}
}
```

### [Referencing the conditional expression's result](#referencing-the-conditional-expressions-result)

The `@if` conditional supports saving the result of the conditional expression into a variable for reuse inside of the block.

```
@if (user.profile.settings.startDate; as startDate) {
  {{ startDate }}
}
```

This can be useful for referencing longer expressions that would be easier to read and maintain within the template.

## [Repeat content with the `@for` block](#repeat-content-with-the-for-block)

The `@for` block loops through a collection and repeatedly renders the content of a block. The collection can be any JavaScript [iterable](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Iteration_protocols), but Angular has additional performance optimizations for `Array` values.

A typical `@for` loop looks like:

```
@for (item of items; track item.id) {
  {{ item.name }}
}
```

Angular's `@for` block does not support flow-modifying statements like JavaScript's `continue` or `break`.

### [Why is `track` in `@for` blocks important?](#why-is-track-in-for-blocks-important)

The `track` expression allows Angular to maintain a relationship between your data and the DOM nodes on the page. This allows Angular to optimize performance by executing the minimum necessary DOM operations when the data changes.

Using track effectively can significantly improve your application's rendering performance when looping over data collections.

Select a property that uniquely identifies each item in the `track` expression. If your data model includes a uniquely identifying property, commonly `id` or `uuid`, use this value. If your data does not include a field like this, strongly consider adding one.

For static collections that never change, you can use `$index` to tell Angular to track each item by its index in the collection.

If no other option is available, you can use the item itself as a tracking key. This tells Angular to track the item by its reference identity using the triple-equals operator (`===`). Avoid this option whenever possible as it can lead to significantly slower rendering updates, as Angular has no way to map which data item corresponds to which DOM nodes.

```
@for (item of items; track item) {
  {{ item.name }}
}
```

**NOTE:** Unlike `*ngFor`, the `@for` block prioritizes view reuse. If a tracked property changes but the object reference remains the same, Angular updates the view's bindings (including component inputs) rather than destroying and recreating the entire element.

### [Contextual variables in `@for` blocks](#contextual-variables-in-for-blocks)

Inside `@for` blocks, several implicit variables are always available:

| Variable | Meaning                                       |
| -------- | --------------------------------------------- |
| `$count` | Number of items in a collection iterated over |
| `$index` | Index of the current row                      |
| `$first` | Whether the current row is the first row      |
| `$last`  | Whether the current row is the last row       |
| `$even`  | Whether the current row index is even         |
| `$odd`   | Whether the current row index is odd          |

These variables are always available with these names, but can be aliased via a `let` segment:

```
@for (item of items; track item.id; let idx = $index, e = $even) {
  <p>Item #{{ idx }}: {{ item.name }}</p>
}
```

The aliasing is useful when nesting `@for` blocks, letting you read variables from the outer `@for` block from an inner `@for` block.

### [Providing a fallback for `@for` blocks with the `@empty` block](#providing-a-fallback-for-for-blocks-with-the-empty-block)

You can optionally include an `@empty` section immediately after the `@for` block content. The content of the `@empty` block displays when there are no items:

```
@for (item of items; track item.name) {
  <li>{{ item.name }}</li>
} @empty {
  <li>There are no items.</li>
}
```

## [Conditionally display content with the `@switch` block](#conditionally-display-content-with-the-switch-block)

While the `@if` block is great for most scenarios, the `@switch` block provides an alternate syntax to conditionally render data. Its syntax closely resembles JavaScript's `switch` statement.

```
@switch (userPermissions) {
  @case ('admin') {
    <app-admin-dashboard />
  }
  @case ('reviewer')
  @case ('editor') {
    <app-editor-dashboard />
  }
  @default {
    <app-viewer-dashboard />
  }
}
```

The value of the conditional expression is compared to the case expression using the triple-equals (`===`) operator.

**`@switch` does not have a fallthrough**, so you do not need an equivalent to a `break` or `return` statement in the block.

You can specify multiple conditions for a single block by having consecutive `@case` statements.

You can optionally include a `@default` block. The content of the `@default` block displays if none of the preceding case expressions match the switch value.

If no `@case` matches the expression and there is no `@default` block, nothing is shown.

### [Exhaustive type checking](#exhaustive-type-checking)

`@switch` supports exhaustive type checking, allowing Angular to verify at compile time that all possible values of a union type are handled.

By using `@default never;`, you explicitly declare that no remaining cases should exist. If the union type is later extended and a new case is not covered by an @case, Angular’s template type checker will report an error, helping you catch missing branches early.

**NOTE:** Exhaustiveness checking relies on TypeScript's type narrowing, which only works on variables. It will not work if the switch condition is a function call or a signal (for example, `@switch (state())`). To work around this, assign the signal to a `@let` variable, e.g.: `@let mySignal = this.mySignal()`.

```
@Component({
  template: `
    @switch (state) {
      @case ('loggedOut') {
        <button>Login</button>
      }

      @case ('loggedIn') {
        <p>Welcome back!</p>
      }

      @default never;
      <!-- throws because @case ('loading') is missing -->
    }
  `,
})
export class AppComponent {
  state: 'loggedOut' | 'loading' | 'loggedIn' = 'loggedOut';
}
```

When the switched expression is nested within a union, you must explicitly specify the expression to check for exhaustiveness.

```
@Component({
  template: `
    @switch (state.mode) {
      @case ('show') {
        {{ state.menu }};
      }
      @case ('hide') {}
      @default never(state);
    }
  `,
})
export class App {
  state!: {mode: 'hide'} | {mode: 'show'; menu: number};
}
```

----
url: https://angular.dev/api/core/ModelOptions
----

## [API](/api/core/ModelOptions#api)

```
    
      interface ModelOptions {}
    
    
```

Optional public name of the input side of the model. The output side will have the same name as the input, but suffixed with `Change`. By default, the class field name is used.

A debug name for the model signal. Used in Angular DevTools to identify the signal.

Jump to details

----
url: https://angular.dev/api/common/testing/MOCK_PLATFORM_LOCATION_CONFIG
----

```
    
      const MOCK_PLATFORM_LOCATION_CONFIG: InjectionToken<MockPlatformLocationConfig>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/DirectiveWithBindings
----

```
    
      interface DirectiveWithBindings<T> {}
    
    
```

Directive type that should be created.

Bindings that should be applied to the specific directive.

Jump to details

----
url: https://angular.dev/api/forms/signals/debounce
----

```
function debounce<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  config: number | 'blur' | Debouncer<TValue, TPathKind>,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to debounce.

@paramconfig`number | "blur" | Debouncer<TValue, TPathKind>`

A debounce configuration, which can be either a debounce duration in milliseconds, `'blur'` to debounce until the field is blurred, or a custom [`Debouncer`](/api/forms/signals/Debouncer) function.

@returns`void`

## [Description](#description)

Configures the frequency at which a form field is updated by UI events.

When this rule is applied, updates from the UI to the form model will be delayed until either the field is touched, or the most recently debounced update resolves.

Jump to details

----
url: https://angular.dev/api/platform-server/platformServer
----

```
function platformServer(
  extraProviders?: StaticProvider[] | undefined,
): PlatformRef;
```

@paramextraProviders`StaticProvider[] | undefined`

@returns`PlatformRef`

## [Description](#description)

Creates a server-side instance of an Angular platform.

This platform should be used when performing server-side rendering of an Angular application. Standalone applications can be bootstrapped on the server using the `bootstrapApplication` function from `@angular/platform-browser`. When using `bootstrapApplication`, the [`platformServer`](/api/platform-server/platformServer) should be created first and passed to the bootstrap function using the [`BootstrapContext`](/api/platform-browser/BootstrapContext).

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/custom-service-worker-scripts
----

While the Angular service worker provides excellent capabilities, you may need to add custom functionality such as handling push notifications, background sync, or other service worker events. You can create a custom service worker script that imports and extends the Angular service worker.

## [Creating a custom service worker](#creating-a-custom-service-worker)

To create a custom service worker that extends Angular's functionality:

1. Create a custom service worker file (e.g., `custom-sw.js`) in your `src` directory:

```
// Import the Angular service worker
importScripts('./ngsw-worker.js');

(function () {
  'use strict';

  // Add custom notification click handler
  self.addEventListener('notificationclick', (event) => {
    console.log('Custom notification click handler');
    console.log('Notification details:', event.notification);

    // Handle notification click - open URL if provided
    if (clients.openWindow && event.notification.data.url) {
      event.waitUntil(clients.openWindow(event.notification.data.url));
      console.log('Opening URL:', event.notification.data.url);
    }
  });

  // Add custom background sync handler
  self.addEventListener('sync', (event) => {
    console.log('Custom background sync handler');

    if (event.tag === 'background-sync') {
      event.waitUntil(doBackgroundSync());
    }
  });

  function doBackgroundSync() {
    // Implement your background sync logic here
    return fetch('https://example.com/api/sync')
      .then((response) => response.json())
      .then((data) => console.log('Background sync completed:', data))
      .catch((error) => console.error('Background sync failed:', error));
  }
})();
```

2. Update your `angular.json` file to use the custom service worker:

```
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "assets": [
              {
                "glob": "**/*",
                "input": "public"
              },
              "app/src/custom-sw.js"
            ]
          }
        }
      }
    }
  }
}
```

3. Configure the service worker registration to use your custom script:

```
import {ApplicationConfig, isDevMode} from '@angular/core';
import {provideServiceWorker} from '@angular/service-worker';

export const appConfig: ApplicationConfig = {
  providers: [
    provideServiceWorker('custom-sw.js', {
      enabled: !isDevMode(),
      registrationStrategy: 'registerWhenStable:30000',
    }),
  ],
};
```

### [Best practices for custom service workers](#best-practices-for-custom-service-workers)

When extending the Angular service worker:

* **Always import the Angular service worker first** using `importScripts('./ngsw-worker.js')` to ensure you get all the caching and update functionality
* **Wrap your custom code in an IIFE** (Immediately Invoked Function Expression) to avoid polluting the global scope
* **Use `event.waitUntil()`** for asynchronous operations to ensure they complete before the service worker is terminated
* **Test thoroughly** in both development and production environments
* **Handle errors gracefully** to prevent your custom code from breaking the Angular service worker functionality

### [Common use cases](#common-use-cases)

Custom service workers are commonly used for:

* **Push notifications**: Handle incoming push messages and display notifications
* **Background sync**: Sync data when the network connection is restored
* **Custom navigation**: Handle special routing or offline page scenarios

----
url: https://angular.dev/errors/NG0503
----

This error means that during the server-side serialization process, Angular encountered nodes that were created outside of Angular's context (i.e. using native DOM APIs) and found those nodes were provided as projectable nodes. They were likely provided using [`ViewContainerRef.createComponent`](/api/core/ViewContainerRef#createComponent) or [`createComponent`](/api/core/createComponent) APIs. Angular hydration does not support this use case.

More information about hydration can be found in [this guide](guide/hydration).

The following examples will trigger the error.

```
@Component({
  selector: 'dynamic',
  template: `<ng-content />`,
})
class CardWrapper {}

@Component({
  selector: 'app',
  template: `<div #target></div>`,
})
class DynamicCard {
  @ViewChild('target', {read: ViewContainerRef}) vcr!: ViewContainerRef;
  envInjector = inject(EnvironmentInjector);

  ngAfterViewInit() {
    const div = document.createElement('div');
    const p = document.createElement('p');
    // In this test we create DOM nodes outside of Angular context
    // (i.e. not using Angular APIs) and try to content-project them.
    // This is an unsupported pattern and an exception will be thrown.
    const compRef = createComponent(CardWrapper, {
      environmentInjector: this.envInjector,
      projectableNodes: [[div, p]],
    });
  }
}
```

## [Debugging the error](#debugging-the-error)

The error message in the developer console should contain information on a specific part of the application's DOM structure that is causing the problem. Review that part of the application for hydration-related errors, such as direct DOM manipulation using native APIs.

You can add the `ngSkipHydration` attribute to your component host node as a possible workaround.

----
url: https://angular.dev/api/forms/signals/SchemaOrSchemaFn
----

```
    
      type SchemaOrSchemaFn<TModel, TPathKind extends PathKind = PathKind.Root> = Schema<TModel> | SchemaFn<TModel, TPathKind>
    
    
```

Jump to details

----
url: https://angular.dev/api/router/Event
----

```
    
      type Event = | NavigationStart  | NavigationEnd  | NavigationCancel  | NavigationError  | RoutesRecognized  | GuardsCheckStart  | GuardsCheckEnd  | RouteConfigLoadStart  | RouteConfigLoadEnd  | ChildActivationStart  | ChildActivationEnd  | ActivationStart  | ActivationEnd  | Scroll  | ResolveStart  | ResolveEnd  | NavigationSkipped
    
    
```

## [Description](#description)

Router events that allow you to track the lifecycle of the router.

The events occur in the following sequence:

* [NavigationStart](api/router/NavigationStart): Navigation starts.
* [RouteConfigLoadStart](api/router/RouteConfigLoadStart): Before the router [lazy loads](guide/routing/loading-strategies) a route configuration.
* [RouteConfigLoadEnd](api/router/RouteConfigLoadEnd): After a route has been lazy loaded.
* [RoutesRecognized](api/router/RoutesRecognized): When the router parses the URL and the routes are recognized.
* [GuardsCheckStart](api/router/GuardsCheckStart): When the router begins the *guards* phase of routing.
* [ChildActivationStart](api/router/ChildActivationStart): When the router begins activating a route's children.
* [ActivationStart](api/router/ActivationStart): When the router begins activating a route.
* [GuardsCheckEnd](api/router/GuardsCheckEnd): When the router finishes the *guards* phase of routing successfully.
* [ResolveStart](api/router/ResolveStart): When the router begins the *resolve* phase of routing.
* [ResolveEnd](api/router/ResolveEnd): When the router finishes the *resolve* phase of routing successfully.
* [ChildActivationEnd](api/router/ChildActivationEnd): When the router finishes activating a route's children.
* [ActivationEnd](api/router/ActivationEnd): When the router finishes activating a route.
* [NavigationEnd](api/router/NavigationEnd): When navigation ends successfully.
* [NavigationCancel](api/router/NavigationCancel): When navigation is canceled.
* [NavigationError](api/router/NavigationError): When navigation fails due to an unexpected error.
* [Scroll](api/router/Scroll): When the user scrolls.

Jump to details

----
url: https://angular.dev/tutorials/deferrable-views/2-loading-error-placeholder
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/deferrable-views/1-what-are-deferrable-views)[](/tutorials/deferrable-views/3-defer-triggers)

[](/tutorials/deferrable-views/1-what-are-deferrable-views)[](/tutorials/deferrable-views/3-defer-triggers)

Deferrable views let you define content to be shown in different loading states.

| Block          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@placeholder` | By default, defer blocks do not render any content before they are triggered. The `@placeholder` is an optional block that declares content to show before the deferred content loads. Angular replaces the placeholder with the deferred content after loading completes. While this block is optional, the Angular team recommends always including a placeholder. [Learn more in the full deferrable views documentation](guide/templates/defer) |
| `@loading`     | This optional block allows you to declare content to be shown during the loading of any deferred dependencies.                                                                                                                                                                                                                                                                                                                                      |
| `@error`       | This block allows you to declare content which is shown if deferred loading fails.                                                                                                                                                                                                                                                                                                                                                                  |

The contents of all the above sub-blocks are eagerly loaded. Additionally, some features require a `@placeholder` block.

In this activity, you'll learn how to use the `@loading`, `@error` and `@placeholder` blocks to manage the states of deferrable views.

***

1. ### [Add `@placeholder` block](#add-placeholder-block)

   In your `app.ts`, add a `@placeholder` block to the `@defer` block.

   ```
   @defer {
     <article-comments />
   } @placeholder {
     <p>Placeholder for comments</p>
   }
   ```

2. ### [Configure the `@placeholder` block](#configure-the-placeholder-block)

   The `@placeholder` block accepts an optional parameter to specify the `minimum` amount of time that this placeholder should be shown. This `minimum` parameter is specified in time increments of milliseconds (ms) or seconds (s). This parameter exists to prevent fast flickering of placeholder content in the case that the deferred dependencies are fetched quickly.

   ```
   @defer {
     <article-comments />
   } @placeholder (minimum 1s) {
     <p>Placeholder for comments</p>
   }
   ```

3. ### [Add `@loading` block](#add-loading-block)

   Next add a `@loading` block to the component template.

   The `@loading` block accepts two optional parameters:

   * `minimum`: the amount of time that this block should be shown
   * `after`: the amount of time to wait after loading begins before showing the loading template

   Both parameters are specified in time increments of milliseconds (ms) or seconds (s).

   Update `app.ts` to include a `@loading` block with a minimum parameter of `1s` as well as an after parameter with the value 500ms to the @loading block.

   ```
   @defer {
     <article-comments />
   } @placeholder (minimum 1s) {
     <p>Placeholder for comments</p>
   } @loading (minimum 1s; after 500ms) {
     <p>Loading comments...</p>
   }
   ```

   **NOTE:** this example uses two parameters, separated by the ; character.

4. ### [Add `@error` block](#add-error-block)

   Finally, add an `@error` block to the `@defer` block.

   ```
   @defer {
     <article-comments />
   } @placeholder (minimum 1s) {
     <p>Placeholder for comments</p>
   } @loading (minimum 1s; after 500ms) {
     <p>Loading comments...</p>
   } @error {
     <p>Failed to load comments</p>
   }
   ```

Congratulations! At this point, you have a good understanding about deferrable views. Keep up the great work and let's learn about triggers next.

----
url: https://angular.dev/api/core/InputOptionsWithTransform
----

```
    
      type InputOptionsWithTransform<T, TransformT> = Required<  Pick<InputOptions<T, TransformT>, 'transform'>> &  InputOptions<T, TransformT>
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDrag
----

```
    
      class CdkDrag<T = any> implements AfterViewInit ,OnChanges ,OnDestroy {}
    
    
```

Arbitrary data to attach to this drag instance.

Locks the position of the dragged element along the specified axis.

Selector that will be used to determine the root draggable element, starting from the `cdkDrag` element and going up the DOM. Passing an alternate root element is useful when trying to enable dragging on an element that you might not have access to.

Node or selector that will be used to determine the element to which the draggable's position will be constrained. If a string is passed in, it'll be used as a selector that will be matched starting from the element's parent and going up the DOM until a match has been found.

Amount of milliseconds to wait after the user has put their pointer down before starting to drag the element.

Sets the position of a [`CdkDrag`](/api/cdk/drag-drop/CdkDrag) that is outside of a drop container. Can be used to restore the element's position for a returning user.

Whether starting to drag this element is disabled.

Function that can be used to customize the logic of how the position of the drag item is limited while it's being dragged. Gets called with a point containing the current position of the user's pointer on the page, a reference to the item being dragged and its dimensions. Should return a point describing where the item should be rendered.

Class to be added to the preview element.

Configures the place into which the preview of the item will be inserted. Can be configured globally through [`CDK_DROP_LIST`](/api/cdk/drag-drop/CDK_DROP_LIST). Possible values:

* `global` - Preview will be inserted at the bottom of the `<body>`. The advantage is that you don't have to worry about `overflow: hidden` or `z-index`, but the item won't retain its inherited styles.
* `parent` - Preview will be inserted into the parent of the drag item. The advantage is that inherited styles will be preserved, but it may be clipped by `overflow: hidden` or not be visible due to `z-index`. Furthermore, the preview is going to have an effect over selectors like `:nth-child` and some flexbox configurations.
* `ElementRef<HTMLElement> | HTMLElement` - Preview will be inserted into a specific element. Same advantages and disadvantages as `parent`.

If the parent of the dragged element has a `scale` transform, it can throw off the positioning when the user starts dragging. Use this input to notify the CDK of the scale.

Returns the element that is being used as a placeholder while the current element is being dragged.

@returns`HTMLElement`

Returns the root draggable element.

@returns`HTMLElement`

Resets a standalone drag item to its initial position.

@returns`void`

Resets drag item to end of boundary element.

@returns`void`

Gets the pixel coordinates of the draggable outside of a drop container.

@returns`Readonly<Point>`

Sets the current position in pixels the draggable outside of a drop container.

@paramvalue`Point`

New position to be set.

@returns`void`

@returns`void`

@paramchanges`SimpleChanges<this>`

@returns`void`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/EventType
----

```
    
      enum EventType {}
    
    
```

Jump to details

----
url: https://angular.dev/essentials/dependency-injection
----

# Dependency Injection

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/dependency-injection.md "Edit this page")

Reuse code and control behaviors across your application and tests.

When you need to share logic between components, Angular leverages the design pattern of [dependency injection](guide/di) that allows you to create a “service” which allows you to inject code into components while managing it from a single source of truth.

## [What are services?](#what-are-services)

Services are reusable pieces of code that can be injected.

Similar to defining a component, services are comprised of the following:

* A **TypeScript decorator** that declares the class as an Angular service via [`@Service`](/api/core/Service) and allows you to define a service that can be accessed anywhere in your application.
* A **TypeScript class** that defines the desired code that will be accessible when the service is injected

Here is an example of a `Calculator` service.

```
import {Service} from '@angular/core';

@Service()
export class Calculator {
  add(x: number, y: number) {
    return x + y;
  }
}
```

## [How to use a service](#how-to-use-a-service)

When you want to use a service in a component, you need to:

1. Import the service
2. Declare a class field where the service is injected. Assign the class field to the result of the call of the built-in function [`inject`](/api/core/inject) which creates the service

Here’s what it might look like in the `Receipt` component:

```
import {Component, inject} from '@angular/core';
import {Calculator} from './calculator';

@Component({
  selector: 'app-receipt',
  template: `<h1>The total is {{ totalCost }}</h1>`,
})
export class Receipt {
  private calculator = inject(Calculator);
  totalCost = this.calculator.add(50, 25);
}
```

In this example, the `Calculator` is being used by calling the Angular function [`inject`](/api/core/inject) and passing in the service to it.

## [Next Step](#next-step)

----
url: https://angular.dev/api/core/afterNextRender
----

```
function afterNextRender<E = never, W = never, M = never>(
  spec: {
    earlyRead?: (() => E) | undefined;
    write?: ((...args: [E] extends [never] ? [] : [E]) => W) | undefined;
    mixedReadWrite?:
      | ((
          ...args: [W] extends [never] ? ([E] extends [never] ? [] : [E]) : [W]
        ) => M)
      | undefined;
    read?:
      | ((
          ...args: [M] extends [never]
            ? [W] extends [never]
              ? [E] extends [never]
                ? []
                : [E]
              : [W]
            : [M]
        ) => void)
      | undefined;
  },
  options?: AfterRenderOptions | undefined,
): AfterRenderRef;
function afterNextRender(
  callback: VoidFunction,
  options?: AfterRenderOptions | undefined,
): AfterRenderRef;
```

Register callbacks to be invoked the next time the application finishes rendering, during the specified phases. The available phases are:

* `earlyRead` Use this phase to **read** from the DOM before a subsequent `write` callback, for example to perform custom layout that the browser doesn't natively support. Prefer the `read` phase if reading can wait until after the write phase. **Never** write to the DOM in this phase.
* `write` Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.
* `mixedReadWrite` Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if it is possible to divide the work among the other phases instead.
* `read` Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.

You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite` phases when possible, to avoid performance degradation.

Note that:

* Callbacks run in the following phase order *once, after the next render*:

Use [`afterNextRender`](/api/core/afterNextRender) to read or write the DOM once, for example to initialize a non-Angular library.

### [Example](#example)

```
@Component({
  selector: 'my-chart-cmp',
  template: `<div #chart>{{ ... }}</div>`,
})
export class MyChartCmp {
  chartRef = viewChild.required<ElementRef>('chart');
  chart: MyChart|null;

  constructor() {
    afterNextRender({
      write: () => {
        this.chart = new MyChart(this.chartRef().nativeElement);
      }
    });
  }
}
```

Register a callback to be invoked the next time the application finishes rendering, during the `mixedReadWrite` phase.

You should prefer specifying an explicit phase for the callback instead, or you risk significant performance degradation.

Note that the callback will run

* in the order it was registered

Use [`afterNextRender`](/api/core/afterNextRender) to read or write the DOM once, for example to initialize a non-Angular library.

### [Example](#example)

```
@Component({
  selector: 'my-chart-cmp',
  template: `<div #chart>{{ ... }}</div>`,
})
export class MyChartCmp {
  chartRef = viewChild.required<ElementRef>('chart');
  chart: MyChart|null;

  constructor() {
    afterNextRender({
      write: () => {
        this.chart = new MyChart(this.chartRef().nativeElement);
      }
    });
  }
}
```

## [Description](#description)

Register callbacks to be invoked the next time the application finishes rendering, during the specified phases. The available phases are:

* `earlyRead` Use this phase to **read** from the DOM before a subsequent `write` callback, for example to perform custom layout that the browser doesn't natively support. Prefer the `read` phase if reading can wait until after the write phase. **Never** write to the DOM in this phase.
* `write` Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.
* `mixedReadWrite` Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if it is possible to divide the work among the other phases instead.
* `read` Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.

You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite` phases when possible, to avoid performance degradation.

Note that:

* Callbacks run in the following phase order *once, after the next render*:

Use [`afterNextRender`](/api/core/afterNextRender) to read or write the DOM once, for example to initialize a non-Angular library.

### [Example](#example)

```
@Component({
  selector: 'my-chart-cmp',
  template: `<div #chart>{{ ... }}</div>`,
})
export class MyChartCmp {
  chartRef = viewChild.required<ElementRef>('chart');
  chart: MyChart|null;

  constructor() {
    afterNextRender({
      write: () => {
        this.chart = new MyChart(this.chartRef().nativeElement);
      }
    });
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/enableProfiling
----

```
function enableProfiling(): () => void;
```

@returns`() => void`

a function that can be invoked to stop sending profiling data.

## [Description](#description)

Start listening to the Angular's internal performance-related events and route those to the Chrome DevTools performance panel. This enables Angular-specific data visualization when recording a performance profile directly in the Chrome DevTools.

**NOTE:** Integration is enabled in the development mode only, this operation is noop in the production mode.

Jump to details

----
url: https://angular.dev/api/forms/FormControlOptions
----

```
    
      interface FormControlOptions extends AbstractControlOptions {}
    
    
```

Whether to use the initial value used to construct the [`FormControl`](/api/forms/FormControl) as its default value as well. If this option is false or not provided, the default value of a FormControl is `null`. When a FormControl is reset without an explicit value, its value reverts to its default value.

@deprecated

Use `nonNullable` instead.

The list of validators applied to a control.

The list of async validators applied to control.

The event name for control to update upon.

## [Description](#description)

Interface for options provided to a [`FormControl`](/api/forms/FormControl).

This interface extends all options from [`AbstractControlOptions`](/api/forms/AbstractControlOptions), plus some options unique to [`FormControl`](/api/forms/FormControl).

Jump to details

----
url: https://angular.dev/api/aria/tabs/TabContent
----

```
    
      class TabContent {}
    
    
```

## [Description](#description)

A TabContent container for the lazy-loaded content.

This structural directive should be applied to an `ng-template` within an `ngTabPanel`. It enables lazy loading of the tab's content, meaning the content is only rendered when the tab is activated for the first time.

```
<div ngTabPanel value="myTabId">
  <ng-template ngTabContent>
    <p>This content will be loaded when 'myTabId' is selected.</p>
  </ng-template>
</div>
```

Jump to details

----
url: https://angular.dev/best-practices/error-handling
----

As your Angular application runs, some of your code may throw an error. If left unhandled, these errors can lead to unexpected behavior and a nonresponsive UI. This guide covers how Angular deals with errors that are not explicitly caught by your application code. For guidance on writing your own error handling logic within your application, consult best practices for error handling in JavaScript and Angular.

A fundamental principle in Angular's error handling strategy is that errors should be surfaced to developers at the callsite whenever possible. This approach ensures that the code which initiated an operation has the context necessary to understand the error, handle it appropriately, and decide what the appropriate application state should be. By making errors visible at their origin, developers can implement error handling that is specific to the failed operation and has access to relevant information for recovery or providing informative feedback to the end-user. This also helps to avoid the "Overly general error" smell, where errors are reported without sufficient context to understand their cause.

For example, consider a component that fetches user data from an API. The code responsible for making the API call should include error handling (e.g., using a `try...catch` block or the `catchError` operator in RxJS) to manage potential network issues or errors returned by the API. This allows the component to display a user-friendly error message or retry the request, rather than letting the error propagate unhandled.

## [Unhandled errors are reported to the `ErrorHandler`](#unhandled-errors-are-reported-to-the-errorhandler)

Angular reports unhandled errors to the application's root [`ErrorHandler`](/api/core/ErrorHandler). When providing a custom [`ErrorHandler`](/api/core/ErrorHandler), provide it in your [`ApplicationConfig`](/api/core/ApplicationConfig) as part of calling [`bootstrapApplication`](/api/platform-browser/bootstrapApplication).

When building an Angular application, often you write code that is called automatically *by* the framework. For example, Angular is responsible for calling a component's constructor and lifecycle methods when that component appears in a template. When the framework runs your code, there's nowhere you could reasonably add a `try` block to gracefully handle errors. In situations like this, Angular catches errors and sends them to the [`ErrorHandler`](/api/core/ErrorHandler).

Angular does *not* catch errors inside of APIs that are called directly by your code. For example, if you have a service with a method that throws an error and you call that method in your component, Angular will not automatically catch that error. You are responsible for handling it using mechanisms like `try...catch`.

Angular catches *asynchronous* errors from user promises or observables only when:

* There is an explicit contract for Angular to wait for and use the result of the asynchronous operation, and
* When errors are not presented in the return value or state.

For example, [`AsyncPipe`](/api/common/AsyncPipe) and [`PendingTasks.run`](/api/core/PendingTasks#run) forward errors to the [`ErrorHandler`](/api/core/ErrorHandler), whereas [`resource`](/api/core/resource) presents the error in the `status` and `error` properties.

Errors that Angular reports to the [`ErrorHandler`](/api/core/ErrorHandler) are *unexpected* errors. These errors may be unrecoverable or an indication that the state of the application is corrupted. Applications should provide error handling using `try` blocks or appropriate error handling operators (like `catchError` in RxJS) where the error occurs whenever possible rather than relying on the [`ErrorHandler`](/api/core/ErrorHandler), which is most frequently and appropriately used only as a mechanism to report potentially fatal errors to the error tracking and logging infrastructure.

```
export class GlobalErrorHandler implements ErrorHandler {
  private readonly analyticsService = inject(AnalyticsService);
  private readonly router = inject(Router);

  handleError(error: any) {
    const url = this.router.url;
    const errorMessage = error?.message ?? 'unknown';

    this.analyticsService.trackEvent({
      eventName: 'exception',
      description: `Screen: ${url} | ${errorMessage}`,
    });

    console.error(GlobalErrorHandler.name, {error});
  }
}
```

### [`TestBed` rethrows errors by default](#testbed-rethrows-errors-by-default)

In many cases, [`ErrorHandler`](/api/core/ErrorHandler) may only log errors and otherwise allow the application to continue running. In tests, however, you almost always want to surface these errors. Angular's [`TestBed`](/api/core/testing/TestBed) rethrows unexpected errors to ensure that errors caught by the framework cannot be unintentionally missed or ignored. In rare circumstances, a test may specifically attempt to ensure errors do not cause the application to be unresponsive or crash. In these situations, you can [configure `TestBed` to *not* rethrow application errors](api/core/testing/TestModuleMetadata#rethrowApplicationErrors) with [`TestBed.configureTestingModule({rethrowApplicationErrors: false})`](/api/core/testing/TestBed#configureTestingModule).

### [Errors thrown while your app is still starting up](#errors-thrown-while-your-app-is-still-starting-up)

There's a brief moment when Angular can't send errors to your [`ErrorHandler`](/api/core/ErrorHandler) yet: while it is still creating your app's root module or root component. Angular needs that root instance to exist before it can look up the [`ErrorHandler`](/api/core/ErrorHandler) you provided, so an error thrown before then has nowhere to go. It behaves like a normal uncaught error instead of being reported through [`ErrorHandler`](/api/core/ErrorHandler).

You're most likely to run into this with [Angular elements](guide/elements). If you define a custom element while its tag is already present on the page, the browser upgrades it right away, running the component's constructor and `ngOnInit` as part of that early startup work. Any error thrown there can get lost.

If this happens to you, move the code that throws so it runs after your app has finished starting up, for example:

* Wrap it in `setTimeout(() => /* your code */)` so it runs on its own turn instead of during startup.
* Provide it as an [`APP_BOOTSTRAP_LISTENER`](/api/core/APP_BOOTSTRAP_LISTENER) instead of running it from a constructor.
* Define your custom elements in `ngDoBootstrap` instead of your root module's constructor.

Turning on [`provideBrowserGlobalErrorListeners()`](/api/core/provideBrowserGlobalErrorListeners) (see below) can also help catch these errors, since they still reach the browser as uncaught errors.

## [Global error listeners](#global-error-listeners)

Errors that are caught neither by the application code nor by the framework's application instance may reach the global scope. Errors reaching the global scope can have unintended consequences if not accounted for. In non-browser environments, they may cause the process to crash. In the browser, these errors may go unreported and site visitors may see the errors in the browser console. Angular provides global listeners for both environments to account for these issues.

### [Client-side rendering](#client-side-rendering)

Adding [`provideBrowserGlobalErrorListeners()`](/api/core/provideBrowserGlobalErrorListeners) to the [ApplicationConfig](guide/di/defining-dependency-providers#application-bootstrap) adds the `'error'` and `'unhandledrejection'` listeners to the browser window and forwards those errors to [`ErrorHandler`](/api/core/ErrorHandler). The Angular CLI generates new applications with this provider by default. The Angular team recommends handling these global errors for most applications, either with the framework's built-in listeners or with your own custom listeners. If you provide custom listeners, you can remove [`provideBrowserGlobalErrorListeners`](/api/core/provideBrowserGlobalErrorListeners).

### [Server-side and hybrid rendering](#server-side-and-hybrid-rendering)

When using [Angular with SSR](guide/ssr), Angular automatically adds the `'unhandledRejection'` and `'uncaughtException'` listeners to the server process. These handlers prevent the server from crashing and instead log captured errors to the console.

**IMPORTANT:** If the application is using Zone.js, only the `'unhandledRejection'` handler is added. When Zone.js is present, errors inside the Application's Zone are already forwarded to the application [`ErrorHandler`](/api/core/ErrorHandler) and do not reach the server process.

----
url: https://angular.dev/api/forms/signals/MIN_LENGTH
----

```
    
      const MIN_LENGTH: LimitKey<number>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/required
----

```
function required<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  config?:
    | (BaseValidatorConfig<TValue, TPathKind> & {
        when?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined;
      })
    | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@paramconfig`(BaseValidatorConfig<TValue, TPathKind> & { when?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined; }) | undefined`

Optional, allows providing any of the following options:

* `message`: A user-facing message for the error.
* `error`: Custom validation error(s) to be used instead of the default [`requiredError()`](/api/forms/signals/requiredError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).
* `when`: A function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns true if the field is required

@returns`void`

## [Description](#description)

Binds a validator to the given path that requires the value to be non-empty. This function can only be called on any type of path. In addition to binding a validator, this function adds [`REQUIRED`](/api/forms/signals/REQUIRED) property to the field.

A value is considered empty when it is `null`, `undefined`, the empty string `''`, `false`, or `NaN`. Every other value is considered non-empty, including `0` and the empty array `[]` — use [`minLength()`](api/forms/signals/minLength) to require a minimum number of items in an array.

`false` is empty to follow the native semantics of `required` on `<input type="checkbox">`, where an unchecked box fails validation. `NaN` is empty because it is usually the result of a parsing error, and is not a valid number.

Jump to details

----
url: https://angular.dev/api/common/http/HttpInterceptor
----

```
    
      interface HttpInterceptor {}
    
    
```

Identifies and handles a given HTTP request.

@paramreq`HttpRequest<any>`

The outgoing request object to handle.

@paramnext`HttpHandler`

The next interceptor in the chain, or the backend if no interceptors remain in the chain.

@returns`Observable<HttpEvent<any>>`

An observable of the event stream.

## [Description](#description)

Intercepts and handles an [`HttpRequest`](/api/common/http/HttpRequest) or [`HttpResponse`](/api/common/http/HttpResponse).

Most interceptors transform the outgoing request before passing it to the next interceptor in the chain, by calling `next.handle(transformedReq)`. An interceptor may transform the response event stream as well, by applying additional RxJS operators on the stream returned by `next.handle()`.

More rarely, an interceptor may handle the request entirely, and compose a new event stream instead of invoking `next.handle()`. This is an acceptable behavior, but keep in mind that further interceptors will be skipped entirely.

It is also rare but valid for an interceptor to return multiple responses on the event stream for a single request.

## [Usage Notes](#usage-notes)

To use the same instance of `HttpInterceptors` for the entire app, import the [`HttpClientModule`](/api/common/http/HttpClientModule) only in your `AppModule`, and add the interceptors to the root application injector. If you import [`HttpClientModule`](/api/common/http/HttpClientModule) multiple times across different modules (for example, in lazy loading modules), each import creates a new copy of the [`HttpClientModule`](/api/common/http/HttpClientModule), which overwrites the interceptors provided in the root module.

Jump to details

----
url: https://angular.dev/api/common/http/HttpParamsOptions
----

```
    
      interface HttpParamsOptions {}
    
    
```

String representation of the HTTP parameters in URL-query-string format. Mutually exclusive with `fromObject`.

Object map of the HTTP parameters. Mutually exclusive with `fromString`.

Encoding codec used to parse and serialize the parameters.

Jump to details

----
url: https://angular.dev/api/forms/MinValidator
----

```
    
      class MinValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the min bound to this directive.

Determines whether this validator should be active or not based on an input. Base class implementation checks whether an input is defined (if the value is different from `null` and `undefined`). Validator classes that extend this base class can override this function with the logic specific to a particular validator directive.

@paraminput`unknown`

@returns`boolean`

## [Description](#description)

A directive which installs the [`MinValidator`](/api/forms/MinValidator) for any `formControlName`, `formControl`, or control with `ngModel` that also has a `min` attribute.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Adding a min validator](#adding-a-min-validator)

The following example shows how to add a min validator to an input attached to an ngModel binding.

```
<input type="number" ngModel min="4">
```

Jump to details

----
url: https://angular.dev/api/platform-browser-dynamic/VERSION
----

* * [JitCompilerFactory](/api/platform-browser-dynamic/JitCompilerFactory)
  * [platformBrowserDynamic](/api/platform-browser-dynamic/platformBrowserDynamic)
  * [VERSION](/api/platform-browser-dynamic/VERSION)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](#api)

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/best-practices/performance/lazy-loaded-routes
----

Understanding how and when routes and components load in Angular routing is crucial for building responsive web applications. Angular offers two primary strategies to control loading behavior:

1. **Eagerly loaded**: Routes and components that are loaded immediately
2. **Lazily loaded**: Routes and components loaded only when needed

Each approach offers distinct advantages for different scenarios.

## [Eagerly loaded components](#eagerly-loaded-components)

When you define a route with the [`component`](api/router/Route#component) property, the referenced component is eagerly loaded as part of the same JavaScript bundle as the route configuration.

```
import {Routes} from '@angular/router';
import {HomePage} from './components/home/home-page';
import {LoginPage} from './components/auth/login-page';

export const routes: Routes = [
  // HomePage and LoginPage are both directly referenced in this config,
  // so their code is eagerly included in the same JavaScript bundle as this file.
  {
    path: '',
    component: HomePage,
  },
  {
    path: 'login',
    component: LoginPage,
  },
];
```

Eagerly loading route components like this means that the browser has to download and parse all of the JavaScript for these components as part of your initial page load, but the components are available to Angular immediately.

While including more JavaScript in your initial page load leads to slower initial load times, this can lead to more seamless transitions as the user navigates through an application.

## [Lazily loaded components and routes](#lazily-loaded-components-and-routes)

You can use the [`loadComponent`](api/router/Route#loadComponent) property to lazily load the JavaScript for a component at the point at which that route would become active. The [`loadChildren`](api/router/Route#loadChildren) property lazily loads child routes during route matching.

```
import {Routes} from '@angular/router';

export const routes: Routes = [
  {
    path: 'login',
    loadComponent: () => import('./components/auth/login-page'),
  },
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin.component'),
    loadChildren: () => import('./admin/admin.routes'),
  },
];
```

The [`loadComponent`](/api/router/Route#loadComponent) and [`loadChildren`](/api/router/Route#loadChildren) properties accept a loader function that returns a Promise that resolves to an Angular component or a set of routes respectively. In most cases, this function uses the standard [JavaScript dynamic import API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import). You can, however, use any arbitrary async loader function.

If the lazily loaded file uses a `default` export, you can return the `import()` promise directly without an additional `.then` call to select the exported class.

Lazily loading routes can significantly improve the load speed of your Angular application by removing large portions of JavaScript from the initial bundle. These portions of your code compile into separate JavaScript "chunks" that the router requests only when the user visits the corresponding route.

## [Injection context lazy loading](#injection-context-lazy-loading)

The Router executes [`loadComponent`](/api/router/Route#loadComponent) and [`loadChildren`](/api/router/Route#loadChildren) within the **injection context of the current route**, allowing you to call [`inject`](/api/core/inject)inside these loader functions to access providers declared on that route, inherited from parent routes through hierarchical dependency injection, or available globally. This enables context-aware lazy loading.

```
import {Routes} from '@angular/router';
import {inject} from '@angular/core';
import {FeatureFlags} from './feature-flags';

export const routes: Routes = [
  {
    path: 'dashboard',
    // Runs inside the route's injection context
    loadComponent: () => {
      const flags = inject(FeatureFlags);
      return flags.isPremium
        ? import('./dashboard/premium-dashboard')
        : import('./dashboard/basic-dashboard');
    },
  },
];
```

## [Should I use an eager or a lazy route?](#should-i-use-an-eager-or-a-lazy-route)

There are many factors to consider when deciding on whether a route should be eager or lazy.

In general, eager loading is recommended for primary landing page(s) while other pages would be lazy-loaded.

**NOTE:** While lazy routes have the upfront performance benefit of reducing the amount of initial data requested by the user, it adds future data requests that could be undesirable. This is particularly true when dealing with nested lazy loading at multiple levels, which can significantly impact performance.

## [Next steps](#next-steps)

Learn how to [display the contents of your routes with Outlets](/guide/routing/show-routes-with-outlets).

----
url: https://angular.dev/errors/NG0751
----

Hot Module Replacement (HMR) is a technique used by development servers to avoid reloading the entire page when only part of an application is changed.

When the HMR is enabled in Angular, all `@defer` block dependencies are loaded eagerly, instead of waiting for configured trigger conditions (both for client-only and incremental hydration triggers). This is needed for the HMR to function properly, replacing components in an application at runtime without the need to reload the entire page. NOTE: the actual rendering of defer blocks respects trigger conditions in the HMR mode.

If you want to test `@defer` block behavior in development mode and ensure that the necessary dependencies are loaded when a triggering condition is met, you can disable the HMR mode as described in [`this document`](/tools/cli/build-system-migration#hot-module-replacement).

----
url: https://angular.dev/reference/migrations/self-closing-tags
----

Self-closing tags are supported in Angular templates since [v16](https://blog.angular.dev/angular-v16-is-here-4d7a28ec680d#7065). .

This schematic migrates the templates in your application to use self-closing tags.

Run the schematic using the following command:

```
ng generate @angular/core:self-closing-tag
```

#### [Before](#before)

```
<hello-world></hello-world>
```

#### [After](#after)

```
<hello-world />
```

----
url: https://angular.dev/api/forms/signals/FormField
----

```
    
      class FormField<T> {}
    
    
```

The field to bind to the underlying form control.

[`FieldState`](/api/forms/signals/FieldState) for the currently bound field.

The node injector for the DOM element hosting this field binding.

The DOM element hosting this field binding.

Errors associated with this form field.

Focuses this field binding.

By default, this will focus the host DOM element. However, custom [`FormUiControl`](/api/forms/signals/FormUiControl)s can implement custom focusing behavior.

@paramoptions`FocusOptions | undefined`

@returns`void`

Resets the bound control.

@returns`void`

Registers this [`FormField`](/api/forms/signals/FormField) as a binding on its associated [`FieldState`](/api/forms/signals/FieldState).

This method should be called at most once for a given [`FormField`](/api/forms/signals/FormField). A [`FormField`](/api/forms/signals/FormField) placed on a custom control ([`FormUiControl`](/api/forms/signals/FormUiControl)) automatically registers that custom control as a binding.

@parambindingOptions`FormFieldBindingOptions | undefined`

@returns`void`

## [Description](#description)

Binds a form [`FieldTree`](/api/forms/signals/FieldTree) to a UI control that edits it. A UI control can be one of several things:

1. A native HTML input or textarea
2. A signal forms custom control that implements [`FormValueControl`](/api/forms/signals/FormValueControl) or [`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl)
3. A component that provides a [`ControlValueAccessor`](/api/forms/ControlValueAccessor). This should only be used for backwards compatibility with reactive forms. Prefer options (1) and (2).

This directive has several responsibilities:

1. Two-way binds the field state's value with the UI control's value
2. Binds additional forms related state on the field state to the UI control (disabled, required, etc.)
3. Relays relevant events on the control to the field state (e.g. marks touched on blur)
4. Provides a fake [`NgControl`](/api/forms/NgControl) that implements a subset of the features available on the reactive forms [`NgControl`](/api/forms/NgControl). This is provided to improve interoperability with controls designed to work with reactive forms. It should not be used by controls written for signal forms.

Jump to details

----
url: https://angular.dev/api/router/NoPreloading
----

```
    
      class NoPreloading implements PreloadingStrategy {}
    
    
```

@paramroute`Route`

@paramfn`() => Observable<any>`

@returns`Observable<any>`

## [Description](#description)

Provides a preloading strategy that does not preload any modules.

This strategy is enabled by default.

Jump to details

----
url: https://angular.dev/api/aria/combobox/COMBOBOX_POPUP
----

```
    
      const COMBOBOX_POPUP: InjectionToken<ComboboxPopup>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/Binding
----

```
    
      interface Binding {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/routing/rendering-strategies
----

This guide helps you choose the right rendering strategy for different parts of your Angular application.

## [What are rendering strategies?](#what-are-rendering-strategies)

Rendering strategies determine when and where your Angular application's HTML content is generated. Each strategy offers different trade-offs between initial page load performance, interactivity, SEO capabilities, and server resource usage.

Angular supports three primary rendering strategies:

* **Client-Side Rendering (CSR)** - Content is rendered entirely in the browser
* **Static Site Generation (SSG/Prerendering)** - Content is pre-rendered at build time
* **Server-Side Rendering (SSR)** - Content is rendered on the server for the initial request for a route

## [Client-Side Rendering (CSR)](#client-side-rendering-csr)

**CSR is Angular's default.** Content renders entirely in the browser after JavaScript loads.

### [When to use CSR](#when-to-use-csr)

✅ It can be a good fit for:

* Interactive applications (dashboards, admin panels)
* Real-time applications
* Internal tools where SEO doesn't matter
* Single-page applications with complex client-side state

❌ When possible, consider avoiding it for:

* Public-facing content that needs SEO
* Pages where initial load performance is critical

### [CSR trade-offs](#csr-trade-offs)

| Aspect            | Impact                                                   |
| :---------------- | :------------------------------------------------------- |
| **SEO**           | Poor - content not visible to crawlers until JS executes |
| **Initial load**  | Slower - must download and execute JavaScript first      |
| **Interactivity** | Immediate once loaded                                    |
| **Server needs**  | Minimal outside of some configuration                    |
| **Complexity**    | Simplest because it works with minimum configuration     |

## [Static Site Generation (SSG/Prerendering)](#static-site-generation-ssg-prerendering)

**SSG pre-renders pages at build time** into static HTML files. The server sends pre-built HTML for the initial page load. After hydration, your app runs entirely in the browser like a traditional SPA - subsequent navigation, route changes, and API calls all happen client-side without server rendering.

### [When to use SSG](#when-to-use-ssg)

✅ It can be a good fit for:

* Marketing pages and landing pages
* Blog posts and documentation
* Product catalogs with stable content
* Content that doesn't change per-user

❌ When possible, consider avoiding it for:

* User-specific content
* Frequently changing data
* Real-time information

### [SSG trade-offs](#ssg-trade-offs)

| Aspect              | Impact                                      |
| :------------------ | :------------------------------------------ |
| **SEO**             | Excellent - full HTML available immediately |
| **Initial load**    | Fastest - pre-generated HTML                |
| **Interactivity**   | After hydration completes                   |
| **Server needs**    | None for serving (CDN-friendly)             |
| **Build time**      | Longer - generates all pages upfront        |
| **Content updates** | Requires rebuild and redeploy               |

📖 **Implementation:** See [Customizing build-time prerendering](guide/ssr#customizing-build-time-prerendering-ssg) in the SSR guide.

## [Server-Side Rendering (SSR)](#server-side-rendering-ssr)

**SSR generates HTML on the server for the initial request for a route**, providing dynamic content with good SEO. The server renders HTML and sends it to the client.

Once the client renders the page, Angular [hydrates](/guide/hydration#what-is-hydration) the app and it then runs entirely in the browser like a traditional SPA - subsequent navigation, route changes, and API calls all happen client-side without additional server rendering.

### [When to use SSR](#when-to-use-ssr)

✅ It can be a good fit for:

* E-commerce product pages (dynamic pricing/inventory)
* News sites and social media feeds
* Personalized content that changes frequently

❌ When possible, consider avoiding it for:

* Static content (use SSG instead)
* When server costs are a concern

### [SSR trade-offs](#ssr-trade-offs)

| Aspect              | Impact                                              |
| :------------------ | :-------------------------------------------------- |
| **SEO**             | Excellent - full HTML for crawlers                  |
| **Initial load**    | Fast - immediate content visibility                 |
| **Interactivity**   | Delayed until hydration                             |
| **Server needs**    | Requires server                                     |
| **Personalization** | Full access to user context                         |
| **Server costs**    | Higher - renders on the initial request for a route |

📖 **Implementation:** See [Server routing](guide/ssr#server-routing) and [Authoring server-compatible components](guide/ssr#authoring-server-compatible-components) in the SSR guide.

## [Choosing the Right Strategy](#choosing-the-right-strategy)

### [Decision matrix](#decision-matrix)

| If you need...             | Use this strategy | Why                                              |
| :------------------------- | :---------------- | :----------------------------------------------- |
| **SEO + Static content**   | SSG               | Pre-rendered HTML, fastest load                  |
| **SEO + Dynamic content**  | SSR               | Fresh content on the initial request for a route |
| **No SEO + Interactivity** | CSR               | Simplest, no server needed                       |
| **Mixed requirements**     | Hybrid            | Different strategies per route                   |

## [Making SSR/SSG Interactive with Hydration](#making-ssr-ssg-interactive-with-hydration)

When using SSR or SSG, Angular "hydrates" the server-rendered HTML to make it interactive.

**Available strategies:**

* **Full hydration** - Entire app becomes interactive at once (default)
* **Incremental hydration** - Parts become interactive as needed (better performance)
* **Event replay** - Captures clicks before hydration completes

📖 **Learn more:**

* [Hydration guide](guide/hydration) - Complete hydration setup
* [Incremental hydration](guide/incremental-hydration) - Advanced hydration with `@defer` blocks

## [Next steps](#next-steps)

----
url: https://angular.dev/tools/cli/aot-metadata-errors
----

The following are metadata errors you may encounter, with explanations and suggested corrections.

## [Expression form not supported](#expression-form-not-supported)

**HELPFUL:** The compiler encountered an expression it didn't understand while evaluating Angular metadata.

Language features outside of the compiler's [restricted expression syntax](tools/cli/aot-compiler) can produce this error, as seen in the following example:

```
// ERROR
export class Fooish { … }
…
const prop = typeof Fooish; // typeof is not valid in metadata
  …
  // bracket notation is not valid in metadata
  { provide: 'token', useValue: { [prop]: 'value' } };
  …
```

You can use `typeof` and bracket notation in normal application code. You just can't use those features within expressions that define Angular metadata.

Avoid this error by sticking to the compiler's [restricted expression syntax](tools/cli/aot-compiler) when writing Angular metadata and be wary of new or unusual TypeScript features.

## [Reference to a local (non-exported) symbol](#reference-to-a-local-non-exported-symbol)

**HELPFUL:** Reference to a local (non-exported) symbol 'symbol name'. Consider exporting the symbol.

The compiler encountered a reference to a locally defined symbol that either wasn't exported or wasn't initialized.

Here's a `provider` example of the problem.

```
// ERROR
let foo: number; // neither exported nor initialized

@Component({
  selector: 'my-component',
  template: … ,
  providers: [
    { provide: Foo, useValue: foo }
  ]
})
export class MyComponent {}
```

The compiler generates the component factory, which includes the `useValue` provider code, in a separate module. *That* factory module can't reach back to *this* source module to access the local (non-exported) `foo` variable.

You could fix the problem by initializing `foo`.

```
let foo = 42; // initialized
```

The compiler will [fold](tools/cli/aot-compiler#code-folding) the expression into the provider as if you had written this.

```
providers: [{provide: Foo, useValue: 42}];
```

Alternatively, you can fix it by exporting `foo` with the expectation that `foo` will be assigned at runtime when you actually know its value.

```
// CORRECTED
export let foo: number; // exported

@Component({
  selector: 'my-component',
  template: … ,
  providers: [
    { provide: Foo, useValue: foo }
  ]
})
export class MyComponent {}
```

Adding `export` often works for variables referenced in metadata such as `providers` and `animations` because the compiler can generate *references* to the exported variables in these expressions. It doesn't need the *values* of those variables.

Adding `export` doesn't work when the compiler needs the *actual value* in order to generate code. For example, it doesn't work for the `template` property.

```
// ERROR
export let someTemplate: string; // exported but not initialized

@Component({
  selector: 'my-component',
  template: someTemplate,
})
export class MyComponent {}
```

The compiler needs the value of the `template` property *right now* to generate the component factory. The variable reference alone is insufficient. Prefixing the declaration with `export` merely produces a new error, "[`Only initialized variables and constants can be referenced`](#only-initialized-variables-and-constants)".

## [Only initialized variables and constants](#only-initialized-variables-and-constants)

**HELPFUL:** *Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler.*

The compiler found a reference to an exported variable or static field that wasn't initialized. It needs the value of that variable to generate code.

The following example tries to set the component's `template` property to the value of the exported `someTemplate` variable which is declared but *unassigned*.

```
// ERROR
export let someTemplate: string;

@Component({
  selector: 'my-component',
  template: someTemplate,
})
export class MyComponent {}
```

You'd also get this error if you imported `someTemplate` from some other module and neglected to initialize it there.

```
// ERROR - not initialized there either
import {someTemplate} from './config';

@Component({
  selector: 'my-component',
  template: someTemplate,
})
export class MyComponent {}
```

The compiler cannot wait until runtime to get the template information. It must statically derive the value of the `someTemplate` variable from the source code so that it can generate the component factory, which includes instructions for building the element based on the template.

To correct this error, provide the initial value of the variable in an initializer clause *on the same line*.

```
// CORRECTED
export let someTemplate = '<h1>Greetings from Angular</h1>';

@Component({
  selector: 'my-component',
  template: someTemplate,
})
export class MyComponent {}
```

## [Reference to a non-exported class](#reference-to-a-non-exported-class)

**HELPFUL:** *Reference to a non-exported class `<class name>`.* *Consider exporting the class.*

Metadata referenced a class that wasn't exported.

For example, you may have defined a class and used it as an injection token in a providers array but neglected to export that class.

```
// ERROR
abstract class MyStrategy { }

  …
  providers: [
    { provide: MyStrategy, useValue: … }
  ]
  …
```

Angular generates a class factory in a separate module and that factory [can only access exported classes](tools/cli/aot-compiler#public-or-protected-symbols). To correct this error, export the referenced class.

```
// CORRECTED
export abstract class MyStrategy { }

  …
  providers: [
    { provide: MyStrategy, useValue: … }
  ]
  …
```

## [Destructured variable or constant not supported](#destructured-variable-or-constant-not-supported)

**HELPFUL:** *Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring.*

The compiler does not support references to variables assigned by [destructuring](https://www.typescriptlang.org/docs/handbook/variable-declarations.html#destructuring).

For example, you cannot write something like this:

```
// ERROR
import { configuration } from './configuration';

// destructured assignment to foo and bar
const {foo, bar} = configuration;
  …
  providers: [
    {provide: Foo, useValue: foo},
    {provide: Bar, useValue: bar},
  ]
  …
```

To correct this error, refer to non-destructured values.

```
// CORRECTED
import { configuration } from './configuration';
  …
  providers: [
    {provide: Foo, useValue: configuration.foo},
    {provide: Bar, useValue: configuration.bar},
  ]
  …
```

## [Could not resolve type](#could-not-resolve-type)

**HELPFUL:** *The compiler encountered a type and can't determine which module exports that type.*

This can happen if you refer to an ambient type. For example, the `Window` type is an ambient type declared in the global `.d.ts` file.

You'll get an error if you reference it in the component constructor, which the compiler must statically analyze.

```
// ERROR
@Component({ })
export class MyComponent {
  constructor (private win: Window) { … }
}
```

TypeScript understands ambient types so you don't import them. The Angular compiler does not understand a type that you neglect to export or import.

In this case, the compiler doesn't understand how to inject something with the `Window` token.

Do not refer to ambient types in metadata expressions.

If you must inject an instance of an ambient type, you can finesse the problem in four steps:

1. Create an injection token for an instance of the ambient type.
2. Create a factory function that returns that instance.
3. Add a `useFactory` provider with that factory function.
4. Use [`@Inject`](/api/core/Inject) to inject the instance.

Here's an illustrative example.

```
// CORRECTED
import { Inject } from '@angular/core';

export const WINDOW = new InjectionToken('Window');
export function _window() { return window; }

@Component({
  …
  providers: [
    { provide: WINDOW, useFactory: _window }
  ]
})
export class MyComponent {
  constructor (@Inject(WINDOW) private win: Window) { … }
}
```

The `Window` type in the constructor is no longer a problem for the compiler because it uses the `@Inject(WINDOW)` to generate the injection code.

Angular does something similar with the `DOCUMENT` token so you can inject the browser's `document` object (or an abstraction of it, depending upon the platform in which the application runs).

```
import { Inject }   from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Component({ … })
export class MyComponent {
  constructor (@Inject(DOCUMENT) private doc: Document) { … }
}
```

## [Name expected](#name-expected)

**HELPFUL:** *The compiler expected a name in an expression it was evaluating.*

This can happen if you use a number as a property name as in the following example.

```
// ERROR
provider: [{provide: Foo, useValue: {0: 'test'}}];
```

Change the name of the property to something non-numeric.

```
// CORRECTED
provider: [{provide: Foo, useValue: {'0': 'test'}}];
```

## [Unsupported enum member name](#unsupported-enum-member-name)

**HELPFUL:** *Angular couldn't determine the value of the [enum member](https://www.typescriptlang.org/docs/handbook/enums.html) that you referenced in metadata.*

The compiler can understand simple enum values but not complex values such as those derived from computed properties.

```
// ERROR
enum Colors {
  Red = 1,
  White,
  Blue = "Blue".length // computed
}

  …
  providers: [
    { provide: BaseColor,   useValue: Colors.White } // ok
    { provide: DangerColor, useValue: Colors.Red }   // ok
    { provide: StrongColor, useValue: Colors.Blue }  // bad
  ]
  …
```

Avoid referring to enums with complicated initializers or computed properties.

## [Tagged template expressions are not supported](#tagged-template-expressions-are-not-supported)

**HELPFUL:** *Tagged template expressions are not supported in metadata.*

The compiler encountered a JavaScript ES2015 [tagged template expression](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals) such as the following.

```
// ERROR
const expression = 'funky';
const raw = String.raw`A tagged template ${expression} string`;
 …
 template: '<div>' + raw + '</div>'
 …
```

[`String.raw()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/raw) is a *tag function* native to JavaScript ES2015.

The AOT compiler does not support tagged template expressions; avoid them in metadata expressions.

## [Symbol reference expected](#symbol-reference-expected)

**HELPFUL:** *The compiler expected a reference to a symbol at the location specified in the error message.*

This error can occur if you use an expression in the `extends` clause of a class.

----
url: https://angular.dev/guide/hydration
----

## [What is hydration](#what-is-hydration)

Hydration is the process that restores the server-side rendered application on the client. This includes things like reusing the server rendered DOM structures, persisting the application state, transferring application data that was retrieved already by the server, and other processes.

## [Why is hydration important?](#why-is-hydration-important)

Hydration improves application performance by avoiding extra work to re-create DOM nodes. Instead, Angular tries to match existing DOM elements to the application's structure at runtime and reuses DOM nodes when possible. This results in a performance improvement that can be measured using [Core Web Vitals (CWV)](https://web.dev/learn-core-web-vitals/) statistics, such as reducing the First Input Delay ([FID](https://web.dev/fid/)) and Largest Contentful Paint ([LCP](https://web.dev/lcp/)), as well as Cumulative Layout Shift ([CLS](https://web.dev/cls/)). Improving these numbers also affects things like SEO performance.

Without hydration enabled, server-side rendered Angular applications will destroy and re-render the application's DOM, which may result in a visible UI flicker. This re-rendering can negatively impact [Core Web Vitals](https://web.dev/learn-core-web-vitals/) like [LCP](https://web.dev/lcp/) and cause a layout shift. Enabling hydration allows the existing DOM to be re-used and prevents a flicker.

## [How do you enable hydration in Angular](#how-do-you-enable-hydration-in-angular)

Hydration can be enabled for server-side rendered (SSR) applications only. Follow the [Angular SSR Guide](guide/ssr) to enable server-side rendering first.

### [Using Angular CLI](#using-angular-cli)

If you've used Angular CLI to enable SSR (either by enabling it during application creation or later via `ng add @angular/ssr`), the code that enables hydration should already be included into your application.

### [Manual setup](#manual-setup)

If you have a custom setup and didn't use Angular CLI to enable SSR, you can enable hydration manually by visiting your main application component or module and importing [`provideClientHydration`](/api/platform-browser/provideClientHydration) from `@angular/platform-browser`. You'll then add that provider to your app's bootstrapping providers list.

```
import {
  bootstrapApplication,
  provideClientHydration,
} from '@angular/platform-browser';
...

bootstrapApplication(App, {
  providers: [provideClientHydration()]
});
```

Alternatively if you are using NgModules, you would add [`provideClientHydration`](/api/platform-browser/provideClientHydration) to your root app module's provider list.

```
import {provideClientHydration} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

@NgModule({
  declarations: [App],
  exports: [App],
  bootstrap: [App],
  providers: [provideClientHydration()],
})
export class AppModule {}
```

**IMPORTANT:** Make sure that the [`provideClientHydration()`](/api/platform-browser/provideClientHydration) call is also included into a set of providers that is used to bootstrap an application on the **server**. In applications with the default project structure (generated by the `ng new` command), adding a call to the root `AppModule` should be sufficient, since this module is imported by the server module. If you use a custom setup, add the [`provideClientHydration()`](/api/platform-browser/provideClientHydration) call to the providers list in the server bootstrap configuration.

### [Verify that hydration is enabled](#verify-that-hydration-is-enabled)

After you've configured hydration and have started up your server, load your application in the browser.

**HELPFUL:** You will likely need to fix instances of Direct DOM Manipulation before hydration will fully work either by switching to Angular constructs or by using `ngSkipHydration`. See [Constraints](#constraints), [Direct DOM Manipulation](#direct-dom-manipulation), and [How to skip hydration for particular components](#how-to-skip-hydration-for-particular-components) for more details.

While running an application in dev mode, you can confirm hydration is enabled by opening the Developer Tools in your browser and viewing the console. You should see a message that includes hydration-related stats, such as the number of components and nodes hydrated. Angular calculates the stats based on all components rendered on a page, including those that come from third-party libraries.

You can also use [Angular DevTools browser extension](tools/devtools) to see hydration status of components on a page. Angular DevTools also allows to enable an overlay to indicate which parts of the page were hydrated. If there is a hydration mismatch error - DevTools would also highlight a component that caused the error.

## [Capturing and replaying events](#capturing-and-replaying-events)

When an application is rendered on the server, it is visible in a browser as soon as produced HTML loads. Users may assume that they can interact with the page, but event listeners are not attached until hydration completes. The Event Replay feature captures all events that happen before hydration and replays those events once hydration has completed.

Event replay is enabled alongside [incremental hydration](guide/incremental-hydration). If you opt out of incremental hydration with [`withNoIncrementalHydration()`](/api/platform-browser/withNoIncrementalHydration), you can still enable event replay using the [`withEventReplay()`](/api/platform-browser/withEventReplay) function:

```
import {
  provideClientHydration,
  withEventReplay,
  withNoIncrementalHydration,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration(withNoIncrementalHydration(), withEventReplay())],
});
```

### [How event replay works](#how-event-replay-works)

Event Replay is a feature that improves user experience by capturing user events that were triggered before the hydration process is complete. Then those events are replayed, ensuring none of that interaction was lost.

The Event Replay is divided into three main phases:

* **Capturing user interactions**\
  Prior to **Hydration**, Event Replay captures and stores all interactions that the user may perform, such as clicks and other browser native events.

* **Storing events**\
  The **Event Contract** keeps in memory all the interactions recorded in the previous step, ensuring that they are not lost for later replay.

* **Relaunch of events**\
  Once **Hydration** is complete, Angular re-invokes the captured events.

Event replay supports *native browser events*, for example `click`, `mouseover`, and `focusin`. If you'd like to learn more about JSAction, the library that powers event replay, you can read more [on the readme](https://github.com/angular/angular/tree/main/packages/core/primitives/event-dispatch#readme).

This feature ensures a consistent user experience, preventing user actions performed before hydration from being ignored.

## [Constraints](#constraints)

Hydration imposes a few constraints on your application that are not present without hydration enabled. Your application must have the same generated DOM structure on both the server and the client. The process of hydration expects the DOM tree to have the same structure in both places. This also includes whitespaces and comment nodes that Angular produces during the rendering on the server. Those whitespaces and nodes must be present in the HTML generated by the server-side rendering process.

**IMPORTANT:** The HTML produced by the server side rendering operation **must not** be altered between the server and the client.

If there is a mismatch between server and client DOM tree structures, the hydration process will encounter problems attempting to match up what was expected to what is actually present in the DOM. Components that do direct DOM manipulation using native DOM APIs are the most common culprit.

### [Direct DOM Manipulation](#direct-dom-manipulation)

If you have components that manipulate the DOM using native DOM APIs or use `innerHTML` or `outerHTML`, the hydration process will encounter errors. Specific cases where DOM manipulation is a problem are situations like accessing the `document`, querying for specific elements, and injecting additional nodes using `appendChild`. Detaching DOM nodes and moving them to other locations will also result in errors.

This is because Angular is unaware of these DOM changes and cannot resolve them during the hydration process. Angular will expect a certain structure, but it will encounter a different structure when attempting to hydrate. This mismatch will result in hydration failure and throw a DOM mismatch error ([see below](#errors)).

It is best to refactor your component to avoid this sort of DOM manipulation. Try to use Angular APIs to do this work, if you can. If you cannot refactor this behavior, use the `ngSkipHydration` attribute ([described below](#how-to-skip-hydration-for-particular-components)) until you can refactor into a hydration friendly solution.

### [Valid HTML structure](#valid-html-structure)

There are a few cases where if you have a component template that does not have valid HTML structure, this could result in a DOM mismatch error during hydration.

As an example, here are some of the most common cases of this issue.

* `<table>` without a `<tbody>`
* `<div>` inside a `<p>`
* `<a>` inside another `<a>`

If you are uncertain about whether your HTML is valid, you can use a [syntax validator](https://validator.w3.org/) to check it.

**NOTE:** While the HTML standard does not require the `<tbody>` element inside tables, modern browsers automatically create a `<tbody>` element in tables that do not declare one. Because of this inconsistency, always explicitly declare a `<tbody>` element in tables to avoid hydration errors.

### [Preserve Whitespaces Configuration](#preserve-whitespaces-configuration)

When using the hydration feature, we recommend using the default setting of `false` for `preserveWhitespaces`. If this setting is not in your tsconfig, the value will be `false` and no changes are required. If you choose to enable preserving whitespaces by adding `preserveWhitespaces: true` to your tsconfig, it is possible you may encounter issues with hydration. This is not yet a fully supported configuration.

**HELPFUL:** Make sure that this setting is set **consistently** in `tsconfig.server.json` for your server and `tsconfig.app.json` for your browser builds. A mismatched value will cause hydration to break.

If you choose to set this setting in your tsconfig, we recommend to set it only in `tsconfig.app.json` which by default the `tsconfig.server.json` will inherit it from.

### [Custom or Noop Zone.js are not yet supported](#custom-or-noop-zonejs-are-not-yet-supported)

Hydration relies on a signal from Zone.js when it becomes stable inside an application, so that Angular can start the serialization process on the server or post-hydration cleanup on the client to remove DOM nodes that remained unclaimed.

Providing a custom or a "noop" Zone.js implementation may lead to a different timing of the "stable" event, thus triggering the serialization or the cleanup too early or too late. This is not yet a fully supported configuration and you may need to adjust the timing of the `onStable` event in the custom Zone.js implementation.

## [Errors](#errors)

There are several hydration related errors you may encounter ranging from node mismatches to cases when the `ngSkipHydration` was used on an invalid host node. The most common error case that may occur is due to direct DOM manipulation using native APIs that results in hydration being unable to find or match the expected DOM tree structure on the client that was rendered by the server. The other case where you may encounter this type of error was mentioned in the [Valid HTML structure](#valid-html-structure) section earlier. So, make sure the HTML in your templates is using valid structure, and you'll avoid that error case.

For a full reference on hydration related errors, visit the [Errors Reference Guide](/errors).

## [How to skip hydration for particular components](#how-to-skip-hydration-for-particular-components)

Some components may not work properly with hydration enabled due to some of the aforementioned issues, like [Direct DOM Manipulation](#direct-dom-manipulation). As a workaround, you can add the `ngSkipHydration` attribute to a component's tag in order to skip hydrating the entire component.

```
<app-example ngSkipHydration />
```

Alternatively you can set `ngSkipHydration` as a host binding.

```
@Component({
  ...
  host: {ngSkipHydration: 'true'},
})
class ExampleComponent {}
```

The `ngSkipHydration` attribute will force Angular to skip hydrating the entire component and its children. Using this attribute means that the component will behave as if hydration is not enabled, meaning it will destroy and re-render itself.

**HELPFUL:** This will fix rendering issues, but it means that for this component (and its children), you don't get the benefits of hydration. You will need to adjust your component's implementation to avoid hydration-breaking patterns (i.e. Direct DOM Manipulation) to be able to remove the skip hydration annotation.

The `ngSkipHydration` attribute can only be used on component host nodes. Angular throws an error if this attribute is added to other nodes.

Keep in mind that adding the `ngSkipHydration` attribute to your root application component would effectively disable hydration for your entire application. Be careful and thoughtful about using this attribute. It is intended as a last resort workaround. Components that break hydration should be considered bugs that need to be fixed.

## [Hydration Timing and Application Stability](#hydration-timing-and-application-stability)

Application stability is an important part of the hydration process. Hydration and any post-hydration processes only occur once the application has reported stability. There are a number of ways that stability can be delayed. Examples include setting timeouts and intervals, unresolved promises, and pending microtasks. In those cases, you may encounter the [Application remains unstable](errors/NG0506) error, which indicates that your app has not yet reached the stable state after 10 seconds. If you're finding that your application is not hydrating right away, take a look at what is impacting application stability and refactor to avoid causing these delays.

### [Debugging Application Stability](#debugging-application-stability)

The [`provideStabilityDebugging`](/api/core/provideStabilityDebugging) utility helps identify why your application fails to stabilize. This utility is provided by default in dev mode when using [`provideClientHydration`](/api/platform-browser/provideClientHydration). You can also add it manually to the application providers for use in production bundles or when using SSR without hydration, for example. The feature logs information to the console if the application takes longer than expected to stabilize.

```
import {provideStabilityDebugging} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import 'zone.js/plugins/task-tracking'; // Use if you have Zone.js with `provideZoneChangeDetection`

bootstrapApplication(App, {
  providers: [provideStabilityDebugging()],
});
```

When enabled, the utility logs pending tasks ([`PendingTasks`](/api/core/PendingTasks)) to the console. If your application uses Zone.js, you can also import `zone.js/plugins/task-tracking` to see which macrotasks are keeping the Angular Zone from stabilizing. This plugin provides the stack trace of the macrotask creation, effectively helping you identify the source of the delay.

**IMPORTANT:** Angular does not remove the zone.js task tracking plugin or this utility from production bundles. Use them only for temporary debugging of stability issues during development, including for optimized production builds.

## [I18N](#i18n)

**HELPFUL:** By default, Angular will skip hydration for components that use i18n blocks, effectively re-rendering those components from scratch.

To enable hydration for i18n blocks, you can add [`withI18nSupport`](/api/platform-browser/withI18nSupport) to your [`provideClientHydration`](/api/platform-browser/provideClientHydration) call.

```
import {
  bootstrapApplication,
  provideClientHydration,
  withI18nSupport,
} from '@angular/platform-browser';
...

bootstrapApplication(App, {
  providers: [provideClientHydration(withI18nSupport())]
});
```

## [Consistent rendering across server-side and client-side](#consistent-rendering-across-server-side-and-client-side)

Avoid introducing `@if` blocks and other conditionals that display different content when server-side rendering than client-side rendering, such as using an `@if` block with Angular's [`isPlatformBrowser`](/api/common/isPlatformBrowser) function. These rendering differences cause layout shifts, negatively impacting end-user experience and core web vitals.

## [Third Party Libraries with DOM Manipulation](#third-party-libraries-with-dom-manipulation)

There are a number of third party libraries that depend on DOM manipulation to be able to render. D3 charts is a prime example. These libraries worked without hydration, but they may cause DOM mismatch errors when hydration is enabled. For now, if you encounter DOM mismatch errors using one of these libraries, you can add the `ngSkipHydration` attribute to the component that renders using that library.

## [Third Party Scripts with DOM Manipulation](#third-party-scripts-with-dom-manipulation)

Many third party scripts, such as ad trackers and analytics, modify the DOM before hydration can occur. These scripts may cause hydration errors because the page no longer matches the structure expected by Angular. Prefer deferring this type of script until after hydration whenever possible. Consider using [`AfterNextRender`](api/core/afterNextRender) to delay the script until post-hydration processes have occurred.

## [Incremental Hydration](#incremental-hydration)

Incremental hydration is an advanced form of hydration that allows for more granular control over when hydration happens. See the [incremental hydration guide](guide/incremental-hydration) for more information.

----
url: https://angular.dev/reference/migrations/control-flow
----

[Control flow syntax](guide/templates/control-flow) is available from Angular v17. The new syntax is baked into the template, so you don't need to import [`CommonModule`](/api/common/CommonModule) anymore.

This schematic migrates all existing code in your application to use new Control Flow Syntax.

Run the schematic using the following command:

```
ng generate @angular/core:control-flow
```

## [Configuration options](#configuration-options)

The migration supports a few options for fine tuning the migration to your specific needs.

### [`--path`](#--path)

By default, the migration will update your whole Angular CLI workspace. You can limit the migration to a specific sub-directory using this option.

### [`--format`](#--format)

By default, the migration reformats the templates it touches. Disable this option to keep your existing template formatting.

## [Breaking changes](#breaking-changes)

### [`@for` view reuse](#for-view-reuse)

If a property used in the `track` expression of a `@for` block changes but the object reference remains the same (in-place modification), Angular updates the view's bindings (including component inputs) instead of destroying and recreating the element.

This differs from `*ngFor`, which would execute a remount (destroy and recreate) of the element in a similar scenario if the `trackBy` function returned a different value.

----
url: https://angular.dev/guide/templates/let-template-variables
----

Angular has two types of variable declarations in templates: local template variables and template reference variables.

**HELPFUL:** In this guide, “template” does not mean the entire HTML template file. It refers only to a specific template construct or expression within the file.

## [Local template variables with `@let`](#local-template-variables-with-let)

Angular's `@let` syntax allows you to define a local variable and re-use it across a template, similar to the [JavaScript `let` syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let).

### [Using `@let`](#using-let)

Use `@let` to declare a variable whose value is based on the result of a template expression. Angular automatically keeps the variable's value up-to-date with the given expression, similar to [bindings](/guide/templates/binding).

```
@let name = user.name;
@let greeting = 'Hello, ' + name;
@let data = data$ | async;
@let pi = 3.14159;
@let coordinates = {x: 50, y: 100};
@let longExpression =
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit ' +
  'sed do eiusmod tempor incididunt ut labore et dolore magna ' +
  'Ut enim ad minim veniam...';
```

Each `@let` block can declare exactly one variable. You cannot declare multiple variables in the same block with a comma.

### [Referencing the value of `@let`](#referencing-the-value-of-let)

Once you've declared a variable with `@let`, you can reuse it in the same template:

```
@let user = user$ | async;

@if (user) {
  <h1>Hello, {{ user.name }}</h1>
  <user-avatar [photo]="user.photo" />

  <ul>
    @for (snack of user.favoriteSnacks; track snack.id) {
      <li>{{ snack.name }}</li>
    }
  </ul>

  <button (click)="update(user)">Update profile</button>
}
```

### [Assignability](#assignability)

A key difference between `@let` and JavaScript's `let` is that `@let` cannot be reassigned after declaration. However, Angular automatically keeps the variable's value up-to-date with the given expression.

```
@let value = 1;

<!-- Invalid - This does not work! -->
<button (click)="value = value + 1">Increment the value</button>
```

### [Variable scope](#variable-scope)

`@let` declarations are scoped to the current view and its descendants. Angular creates a new view at component boundaries and wherever a template might contain dynamic content, such as control flow blocks, `@defer` blocks, or structural directives.

Since `@let` declarations are not hoisted, they **cannot** be accessed by parent views or siblings:

```
@let topLevel = value;

<div>
  @let insideDiv = value;
</div>

<!-- Valid -->
{{ topLevel }}
<!-- Valid -->
{{ insideDiv }}

@if (condition) {
  <!-- Valid -->
  {{ topLevel + insideDiv }}

  @let nested = value;

  @if (condition) {
    <!-- Valid -->
    {{ topLevel + insideDiv + nested }}
  }
}

<!-- Error, not hoisted from @if -->
{{ nested }}
```

```
<!-- Create a template reference variable named "taskInput", referring to the HTMLInputElement. -->
<input #taskInput placeholder="Enter task name" />
```

### [Assigning values to template reference variables](#assigning-values-to-template-reference-variables)

Angular assigns a value to template variables based on the element on which the variable is declared.

If you declare the variable on a Angular component, the variable refers to the component instance.

```
<!-- The `startDate` variable is assigned the instance of `MyDatepicker`. -->
<my-datepicker #startDate />
```

If you declare the variable on an `<ng-template>` element, the variable refers to a TemplateRef instance which represents the template. For more information, see [How Angular uses the asterisk, \*, syntax](/guide/directives/structural-directives#structural-directive-shorthand) in [Structural directives](/guide/directives/structural-directives).

```
<!-- The `myFragment` variable is assigned the `TemplateRef` instance corresponding to this template fragment. -->
<ng-template #myFragment>
  <p>This is a template fragment</p>
</ng-template>
```

If you declare the variable on any other displayed element, the variable refers to the `HTMLElement` instance.

```
<!-- The "taskInput" variable refers to the HTMLInputElement instance. -->
<input #taskInput placeholder="Enter task name" />
```

#### [Assigning a reference to an Angular directive](#assigning-a-reference-to-an-angular-directive)

Angular directives may have an `exportAs` property that defines a name by which the directive can be referenced in a template:

```
@Directive({
  selector: '[dropZone]',
  exportAs: 'dropZone',
})
export class DropZone {
  /* ... */
}
```

When you declare a template variable on an element, you can assign that variable a directive instance by specifying this `exportAs` name:

```
<!-- The `firstZone` variable refers to the `DropZone` directive instance. -->
<section dropZone #firstZone="dropZone">...</section>
```

You cannot refer to a directive that does not specify an `exportAs` name.

### [Using template reference variables with queries](#using-template-reference-variables-with-queries)

In addition to using template variables to read values from another part of the same template, you can also use this style of variable declaration to "mark" an element for [component and directive queries](/guide/components/queries).

When you want to query for a specific element in a template, you can declare a template variable on that element and then query for the element based on the variable name.

```
<input #description value="Original description" />
```

```
@Component({
  /* ... */,
  template: `<input #description value="Original description">`,
})
export class AppComponent {
  // Query for the input element based on the template variable name.
  @ViewChild('description') input: ElementRef | undefined;
}
```

```
<input #ref1 type="text" [(ngModel)]="firstExample" />

@if (true) {
  <span>Value: {{ ref1.value }}</span>
}
```

In this case, the `@if` block creates a new template scope, which includes the `ref1` variable from its parent scope.

However, accessing a template variable from a child scope in the parent template doesn't work:

Avoid

```
@if (true) {
  <input #ref2 type="text" [(ngModel)]="secondExample" />
}

<span>Value: {{ ref2?.value }}</span>
```

Here, `ref2` is declared in the child scope created by `@if`, and is not accessible from the parent template.

----
url: https://angular.dev/tutorials/first-app/12-forms
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/11-details-page)[](/tutorials/first-app/13-search)

[](/tutorials/first-app/11-details-page)[](/tutorials/first-app/13-search)

This tutorial lesson demonstrates how to add a form that collects user data to an Angular app. This lesson starts with a functional Angular app and shows how to add a form to it.

The data that the form collects is sent only to the app's service, which writes it to the browser's console. Using a REST API to send and receive the form's data is not covered in this lesson.

[](https://www.youtube.com/watch?v=kWbk-dOJaNQ\&autoplay=1)

**IMPORTANT:** We recommend using your local environment for this step of the tutorial.

## [What you'll learn](#what-youll-learn)

* Your app has a form into which users can enter data that is sent to your app's service.
* The service writes the data from the form to the browser's console log.

1. ### [Add a method to send form data](#add-a-method-to-send-form-data)

   This step adds a method to your app's service that receives the form data to send to the data's destination. In this example, the method writes the data from the form to the browser's console log.

   In the **Edit** pane of your IDE:

   1. In `src/app/housing.service.ts`, inside the `HousingService` class, paste this method at the bottom of the class definition.

      ### Submit method in src/app/housing.service.ts

      ```
      import {Service} from '@angular/core';
      import {HousingLocationInfo} from './housinglocation';

      @Service()
      export class HousingService {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        protected housingLocationList: HousingLocationInfo[] = [
          {
            id: 0,
            name: 'Acme Fresh Start Housing',
            city: 'Chicago',
            state: 'IL',
            photo: `${this.baseUrl}/bernard-hermant-CLKGGwIBTaY-unsplash.jpg`,
            availableUnits: 4,
            wifi: true,
            laundry: true,
          },
          {
            id: 1,
            name: 'A113 Transitional Housing',
            city: 'Santa Monica',
            state: 'CA',
            photo: `${this.baseUrl}/brandon-griggs-wR11KBaB86U-unsplash.jpg`,
            availableUnits: 0,
            wifi: false,
            laundry: true,
          },
          {
            id: 2,
            name: 'Warm Beds Housing Support',
            city: 'Juneau',
            state: 'AK',
            photo: `${this.baseUrl}/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg`,
            availableUnits: 1,
            wifi: false,
            laundry: false,
          },
          {
            id: 3,
            name: 'Homesteady Housing',
            city: 'Chicago',
            state: 'IL',
            photo: `${this.baseUrl}/ian-macdonald-W8z6aiwfi1E-unsplash.jpg`,
            availableUnits: 1,
            wifi: true,
            laundry: false,
          },
          {
            id: 4,
            name: 'Happy Homes Group',
            city: 'Gary',
            state: 'IN',
            photo: `${this.baseUrl}/krzysztof-hepner-978RAXoXnH4-unsplash.jpg`,
            availableUnits: 1,
            wifi: true,
            laundry: false,
          },
          {
            id: 5,
            name: 'Hopeful Apartment Group',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/r-architecture-JvQ0Q5IkeMM-unsplash.jpg`,
            availableUnits: 2,
            wifi: true,
            laundry: true,
          },
          {
            id: 6,
            name: 'Seriously Safe Towns',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/phil-hearing-IYfp2Ixe9nM-unsplash.jpg`,
            availableUnits: 5,
            wifi: true,
            laundry: true,
          },
          {
            id: 7,
            name: 'Hopeful Housing Solutions',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/r-architecture-GGupkreKwxA-unsplash.jpg`,
            availableUnits: 2,
            wifi: true,
            laundry: true,
          },
          {
            id: 8,
            name: 'Seriously Safe Towns',
            city: 'Oakland',
            state: 'CA',
            photo: `${this.baseUrl}/saru-robert-9rP3mxf8qWI-unsplash.jpg`,
            availableUnits: 10,
            wifi: false,
            laundry: false,
          },
          {
            id: 9,
            name: 'Capital Safe Towns',
            city: 'Portland',
            state: 'OR',
            photo: `${this.baseUrl}/webaliser-_TPTXZd9mOo-unsplash.jpg`,
            availableUnits: 6,
            wifi: true,
            laundry: true,
          },
        ];

        getAllHousingLocations(): HousingLocationInfo[] {
          return this.housingLocationList;
        }

        getHousingLocationById(id: number): HousingLocationInfo | undefined {
          return this.housingLocationList.find((housingLocation) => housingLocation.id === id);
        }
        submitApplication(firstName: string, lastName: string, email: string) {
          console.log(
            `Homes application received: firstName: ${firstName}, lastName: ${lastName}, email: ${email}.`,
          );
        }
      }
      ```

   2. Confirm that the app builds without error. Correct any errors before you continue to the next step.

2. ### [Add the form functions to the details page](#add-the-form-functions-to-the-details-page)

   This step adds the code to the details page that handles the form's interactions.

   In the **Edit** pane of your IDE, in `src/app/details/details.ts`:

   1. After the `import` statements at the top of the file, add the following code to import the Angular form classes.

      ### Forms imports in src/app/details/details.ts

      ```
      import {Component, inject} from '@angular/core';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocationInfo} from '../housinglocation';
      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';

      @Component({
        selector: 'app-details',
        imports: [ReactiveFormsModule],
        template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
            <section class="listing-apply">
              <h2 class="section-heading">Apply now to live here</h2>
              <form [formGroup]="applyForm" (submit)="submitApplication()">
                <label for="first-name">First Name</label>
                <input id="first-name" type="text" formControlName="firstName" />

                <label for="last-name">Last Name</label>
                <input id="last-name" type="text" formControlName="lastName" />

                <label for="email">Email</label>
                <input id="email" type="email" formControlName="email" />
                <button type="submit" class="primary">Apply now</button>
              </form>
            </section>
          </article>
        `,
        styleUrls: ['./details.css'],
      })
      export class Details {
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocationInfo | undefined;
        applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });

        constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingLocation = this.housingService.getHousingLocationById(housingLocationId);
        }
        submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      }
      ```

   2. In the `Details` decorator metadata, update the `imports` property with the following code:

      ### imports directive in src/app/details/details.ts

      ```
      import {Component, inject} from '@angular/core';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocationInfo} from '../housinglocation';
      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';

      @Component({
        selector: 'app-details',
        imports: [ReactiveFormsModule],
        template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
            <section class="listing-apply">
              <h2 class="section-heading">Apply now to live here</h2>
              <form [formGroup]="applyForm" (submit)="submitApplication()">
                <label for="first-name">First Name</label>
                <input id="first-name" type="text" formControlName="firstName" />

                <label for="last-name">Last Name</label>
                <input id="last-name" type="text" formControlName="lastName" />

                <label for="email">Email</label>
                <input id="email" type="email" formControlName="email" />
                <button type="submit" class="primary">Apply now</button>
              </form>
            </section>
          </article>
        `,
        styleUrls: ['./details.css'],
      })
      export class Details {
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocationInfo | undefined;
        applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });

        constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingLocation = this.housingService.getHousingLocationById(housingLocationId);
        }
        submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      }
      ```

   3. In the `Details` class, before the `constructor()` method, add the following code to create the form object.

      ### template directive in src/app/details/details.ts

      ```
      import {Component, inject} from '@angular/core';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocationInfo} from '../housinglocation';
      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';

      @Component({
        selector: 'app-details',
        imports: [ReactiveFormsModule],
        template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
            <section class="listing-apply">
              <h2 class="section-heading">Apply now to live here</h2>
              <form [formGroup]="applyForm" (submit)="submitApplication()">
                <label for="first-name">First Name</label>
                <input id="first-name" type="text" formControlName="firstName" />

                <label for="last-name">Last Name</label>
                <input id="last-name" type="text" formControlName="lastName" />

                <label for="email">Email</label>
                <input id="email" type="email" formControlName="email" />
                <button type="submit" class="primary">Apply now</button>
              </form>
            </section>
          </article>
        `,
        styleUrls: ['./details.css'],
      })
      export class Details {
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocationInfo | undefined;
        applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });

        constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingLocation = this.housingService.getHousingLocationById(housingLocationId);
        }
        submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      }
      ```

      In Angular, [`FormGroup`](/api/forms/FormGroup) and [`FormControl`](/api/forms/FormControl) are types that enable you to build forms. The [`FormControl`](/api/forms/FormControl) type can provide a default value and shape the form data. In this example `firstName` is a `string` and the default value is empty string.

   4. In the `Details` class, after the `constructor()` method, add the following code to handle the **Apply now** click.

      ### template directive in src/app/details/details.ts

      ```
      import {Component, inject} from '@angular/core';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocationInfo} from '../housinglocation';
      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';

      @Component({
        selector: 'app-details',
        imports: [ReactiveFormsModule],
        template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
            <section class="listing-apply">
              <h2 class="section-heading">Apply now to live here</h2>
              <form [formGroup]="applyForm" (submit)="submitApplication()">
                <label for="first-name">First Name</label>
                <input id="first-name" type="text" formControlName="firstName" />

                <label for="last-name">Last Name</label>
                <input id="last-name" type="text" formControlName="lastName" />

                <label for="email">Email</label>
                <input id="email" type="email" formControlName="email" />
                <button type="submit" class="primary">Apply now</button>
              </form>
            </section>
          </article>
        `,
        styleUrls: ['./details.css'],
      })
      export class Details {
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocationInfo | undefined;
        applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });

        constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingLocation = this.housingService.getHousingLocationById(housingLocationId);
        }
        submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      }
      ```

      This button does not exist yet - you will add it in the next step. In the above code, the [`FormControl`](/api/forms/FormControl)s may return `null`. This code uses the nullish coalescing operator to default to empty string if the value is `null`.

   5. Confirm that the app builds without error. Correct any errors before you continue to the next step.

3. ### [Add the form's markup to the details page](#add-the-forms-markup-to-the-details-page)

   This step adds the markup to the details page that displays the form.

   In the **Edit** pane of your IDE, in `src/app/details/details.ts`:

   1. In the `Details` decorator metadata, update the `template` HTML to match the following code to add the form's markup.

      ### template directive in src/app/details/details.ts

      ```
      import {Component, inject} from '@angular/core';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocationInfo} from '../housinglocation';
      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';

      @Component({
        selector: 'app-details',
        imports: [ReactiveFormsModule],
        template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
            <section class="listing-apply">
              <h2 class="section-heading">Apply now to live here</h2>
              <form [formGroup]="applyForm" (submit)="submitApplication()">
                <label for="first-name">First Name</label>
                <input id="first-name" type="text" formControlName="firstName" />

                <label for="last-name">Last Name</label>
                <input id="last-name" type="text" formControlName="lastName" />

                <label for="email">Email</label>
                <input id="email" type="email" formControlName="email" />
                <button type="submit" class="primary">Apply now</button>
              </form>
            </section>
          </article>
        `,
        styleUrls: ['./details.css'],
      })
      export class Details {
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocationInfo | undefined;
        applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });

        constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingLocation = this.housingService.getHousingLocationById(housingLocationId);
        }
        submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      }
      ```

      The template now includes an event handler `(submit)="submitApplication()"`. Angular uses parentheses syntax around the event name to define events in the template code. The code on the right hand side of the equals sign is the code that should be executed when this event is triggered. You can bind to browser events and custom events.

   2. Confirm that the app builds without error. Correct any errors before you continue to the next step.

4. ### [Test your app's new form](#test-your-apps-new-form)

   This step tests the new form to see that when the form data is submitted to the app, the form data appears in the console log.

   1. In the **Terminal** pane of your IDE, run `ng serve`, if it isn't already running.

   2. In your browser, open your app at `http://localhost:4200`.

   3. Right click on the app in the browser and from the context menu, choose **Inspect**.

   4. In the developer tools window, choose the **Console** tab. Make sure that the developer tools window is visible for the next steps

   5. In your app:

      1. Select a housing location and click **Learn more**, to see details about the house.
      2. In the house's details page, scroll to the bottom to find the new form.
      3. Enter data into the form's fields - any data is fine.
      4. Choose **Apply now** to submit the data.

   6. In the developer tools window, review the log output to find your form data.

**SUMMARY:** In this lesson, you updated your app to add a form using Angular's forms feature, and connect the data captured in the form to a component using an event handler.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/api/core/IterableChanges
----

```
    
      interface IterableChanges<V> {}
    
    
```

Iterate over all changes. [`IterableChangeRecord`](/api/core/IterableChangeRecord) will contain information about changes to each item.

@paramfn`(record: IterableChangeRecord<V>) => void`

@returns`void`

Iterate over a set of operations which when applied to the original `Iterable` will produce the new `Iterable`.

**NOTE:** These are not necessarily the actual operations which were applied to the original `Iterable`, rather these are a set of computed operations which may not be the same as the ones applied.

@paramfn`(record: IterableChangeRecord<V>, previousIndex: number | null, currentIndex: number | null) => void`

@returns`void`

Iterate over changes in the order of original `Iterable` showing where the original items have moved.

@paramfn`(record: IterableChangeRecord<V>) => void`

@returns`void`

Iterate over all added items.

@paramfn`(record: IterableChangeRecord<V>) => void`

@returns`void`

Iterate over all moved items.

@paramfn`(record: IterableChangeRecord<V>) => void`

@returns`void`

Iterate over all removed items.

@paramfn`(record: IterableChangeRecord<V>) => void`

@returns`void`

Iterate over all items which had their identity (as computed by the [`TrackByFunction`](/api/core/TrackByFunction)) changed.

@paramfn`(record: IterableChangeRecord<V>) => void`

@returns`void`

Jump to details

----
url: https://angular.dev/api/common/ImageLoaderConfig
----

```
    
      interface ImageLoaderConfig {}
    
    
```

Image file name to be added to the image request URL.

Width of the requested image (to be used when generating srcset).

Height of the requested image (to be used when generating srcset).

Whether the loader should generate a URL for a small image placeholder instead of a full-sized image.

Additional user-provided parameters for use by the ImageLoader.

Jump to details

----
url: https://angular.dev/api/common/http/HttpUrlEncodingCodec
----

```
    
      class HttpUrlEncodingCodec implements HttpParameterCodec {}
    
    
```

Encodes a key name for a URL parameter or query-string.

@paramkey`string`

The key name.

@returns`string`

The encoded key name.

Encodes the value of a URL parameter or query-string.

@paramvalue`string`

The value.

@returns`string`

The encoded value.

Decodes an encoded URL parameter or query-string key.

@paramkey`string`

The encoded key name.

@returns`string`

The decoded key name.

Decodes an encoded URL parameter or query-string value.

@paramvalue`string`

The encoded value.

@returns`string`

The decoded value.

## [Description](#description)

Provides encoding and decoding of URL parameter and query-string values.

Serializes and parses URL parameter keys and values to encode and decode them. If you pass URL query parameters without encoding, the query parameters can be misinterpreted at the receiving end.

Jump to details

----
url: https://angular.dev/api/core/createPlatform
----

```
function createPlatform(injector: Injector): PlatformRef;
```

@paraminjector`Injector`

@returns`PlatformRef`

Jump to details

----
url: https://angular.dev/api/core/inputBinding
----

```
function inputBinding(publicName: string, value: () => unknown): Binding;
```

@parampublicName`string`

Public name of the input to bind to.

@paramvalue`() => unknown`

Callback that returns the current value for the binding. Can be either a signal or a plain getter function.

### [Usage Example](#usage-example)

In this example we create an instance of the `MyButton` component and bind the value of the `isDisabled` signal to its `disabled` input.

```
const isDisabled = signal(false);

createComponent(MyButton, {
bindings: [inputBinding('disabled', isDisabled)]
});
```

@returns`Binding`

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/outputToObservable
----

```
function outputToObservable<T>(ref: OutputRef<T>): Observable<T>;
```

@paramref`OutputRef<T>`

@returns`Observable<T>`

## [Description](#description)

Converts an Angular output declared via `output()` or [`outputFromObservable()`](/api/core/rxjs-interop/outputFromObservable) to an observable. It creates an observable that represents the stream of "events firing" in an output.

You can subscribe to the output via `Observable.subscribe` then.

Jump to details

----
url: https://angular.dev/api/router/NavigationError
----

```
    
      class NavigationError extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramerror`any`

@paramtarget`RouterStateSnapshot | undefined`

The target of the navigation when the error occurred.

Note that this can be `undefined` because an error could have occurred before the [`RouterStateSnapshot`](/api/router/RouterStateSnapshot) was created for the navigation.

@returns`NavigationError`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/tools/cli/build-system-migration
----

```
ng update @angular/cli --name use-application-builder
```

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
...
```

Changing the `builder` field is the only change you will need to make.

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser-esbuild",
...
```

#### [Manual migration to the new `application` builder](#manual-migration-to-the-new-application-builder)

A builder named `application` is also available within the `@angular-devkit/build-angular` package that is present in an Angular CLI generated application. This builder is the default for all new applications created via `ng new`.

The following is what you would typically find in `angular.json` for an application:

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
...
```

Changing the `builder` field is the first change you will need to make.

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:application",
...
```

```
ng build
```

## [Starting the development server](#starting-the-development-server)

The development server will automatically detect the new build system and use it to build the application. To start the development server no changes are necessary to the `dev-server` builder configuration or command line.

```
ng serve
```

```
ng serve --no-hmr
```

```
"serve": {
  "builder": "@angular/build:dev-server",
  "options": {
    "prebundle": {
      "exclude": ["some-dep"]
    }
  },
```

By default, `prebundle` is set to `true` but can be set to `false` to fully disable prebundling. However, excluding specific dependencies is recommended instead since rebuild times will increase with prebundling disabled.

```
"serve": {
  "builder": "@angular/build:dev-server",
  "options": {
    "prebundle": false
  },
```

## [New features](#new-features)

One of the main benefits of the application build system is the improved build and rebuild speed. However, the new application build system also provides additional features not present in the `browser` builder.

**IMPORTANT:** The new features of the `application` builder described here are incompatible with the `karma` test builder by default because it is using the `browser` builder internally. Users can opt-in to use the `application` builder by setting the `builderMode` option to `application` for the `karma` builder. This option is currently in developer preview. If you notice any issues, please report them [here](https://github.com/angular/angular-cli/issues).

### [Build-time value replacement with `define`](#build-time-value-replacement-with-define)

The `define` option allows identifiers present in the code to be replaced with another value at build time. This is similar to the behavior of Webpack's `DefinePlugin` which was previously used with some custom Webpack configurations that used third-party builders. The option can either be used within the `angular.json` configuration file or on the command line. Configuring `define` within `angular.json` is useful for cases where the values are constant and able to be checked in to source control.

Within the configuration file, the option is in the form of an object. The keys of the object represent the identifier to replace and the values of the object represent the corresponding replacement value for the identifier. An example is as follows:

```
"build": {
  "builder": "@angular/build:application",
  "options": {
    ...
    "define": {
        "SOME_NUMBER": "5",
        "ANOTHER": "'this is a string literal, note the extra single quotes'",
        "REFERENCE": "globalThis.someValue.noteTheAbsentSingleQuotes"
    }
  }
}
```

**HELPFUL:** All replacement values are defined as strings within the configuration file. If the replacement is intended to be an actual string literal, it should be enclosed in single quote marks. This allows the flexibility of using any valid JSON type as well as a different identifier as a replacement.

The command line usage is preferred for values that may change per build execution such as the git commit hash or an environment variable. The CLI will merge `--define` values from the command line with `define` values from `angular.json`, including both in a build. Command line usage takes precedence if the same identifier is present for both. For command line usage, the `--define` option uses the format of `IDENTIFIER=VALUE`.

```
ng build --define SOME_NUMBER=5 --define "ANOTHER='these will overwrite existing'"
```

Environment variables can also be selectively included in a build. For non-Windows shells, the quotes around the hash literal can be escaped directly if preferred. This example assumes a bash-like shell but similar behavior is available for other shells as well.

```
export MY_APP_API_HOST="http://example.com"
export API_RETRY=3
ng build --define API_HOST=\'$MY_APP_API_HOST\' --define API_RETRY=$API_RETRY
```

For either usage, TypeScript needs to be aware of the types for the identifiers to prevent type-checking errors during the build. This can be accomplished with an additional type definition file within the application source code (`src/types.d.ts`, for example) with similar content:

```
declare const SOME_NUMBER: number;
declare const ANOTHER: string;
declare const GIT_HASH: string;
declare const API_HOST: string;
declare const API_RETRY: number;
```

```
"build": {
  "builder": "@angular/build:application",
  "options": {
    ...
    "loader": {
      ".svg": "text"
    }
  }
}
```

An SVG file can then be imported:

```
import contents from './some-file.svg';

console.log(contents); // <svg>...</svg>
```

Additionally, TypeScript needs to be aware of the module type for the import to prevent type-checking errors during the build. This can be accomplished with an additional type definition file within the application source code (`src/types.d.ts`, for example) with the following or similar content:

```
declare module '*.svg' {
  const content: string;
  export default content;
}
```

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import contents from './some-file.svg' with {loader: 'text'};
```

The same can be accomplished with an import expression inside an async function.

```
async function loadSvg(): Promise<string> {
  // @ts-expect-error TypeScript cannot provide types based on attributes yet
  return import('./some-file.svg', {with: {loader: 'text'}}).then((m) => m.default);
}
```

For the import expression, the `loader` value must be a string literal to be statically analyzed. A warning will be issued if the value is not a string literal.

The `file` loader is useful when a file will be loaded at runtime through either a `fetch()`, setting to an image elements `src`, or other similar method.

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import imagePath from './image.webp' with {loader: 'file'};

console.log(imagePath); // media/image-ULK2SIIB.webp
```

The `base64` loader is useful when a file needs to be embedded directly into the bundle as an encoded string that can later be used to construct a Data URL.

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import logo from './logo.png' with {loader: 'base64'};

console.log(logo); // "iVBORw0KGgoAAAANSUhEUgAA..."
```

The `dataurl` loader to inline assets as complete Data URLs.

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import icon from './icon.svg' with {loader: 'dataurl'};

console.log(icon); // "data:image/svg+xml;..."
```

```
import {verboseLogging} from '#logger';
```

The file can be switched in the `imports` field in `package.json`:

```
{
  ...
  "imports": {
    "#logger": {
      "development": "./src/logging/debug.ts",
      "default": "./src/logging/noop.ts"
    }
  }
}
```

For applications that are also using SSR, browser and server code can be switched by using the `browser` condition:

```
{
  ...
  "imports": {
    "#crashReporter": {
      "browser": "./src/browser-logger.ts",
      "default": "./src/server-logger.ts"
    }
  }
}
```

```
import * as moment from 'moment';

console.log(moment().format());
```

The build will generate a warning to notify you that there is a potential problem. The warning will be similar to:

```
▲ [WARNING] Calling "moment" will crash at run-time because it's an import namespace object, not a function [call-import-namespace]

    src/main.ts:2:12:
      2 │ console.log(moment().format());
        ╵             ~~~~~~

Consider changing "moment" to a default import instead:

    src/main.ts:1:7:
      1 │ import * as moment from 'moment';
        │        ~~~~~~~~~~~
        ╵        moment
```

However, you can avoid the runtime errors and the warning by enabling the `esModuleInterop` TypeScript option for the application and changing the import to the following:

```
import moment from 'moment';

console.log(moment().format());
```

----
url: https://angular.dev/ai/design-patterns
----

Interacting with AI and Large Language Model (LLM) APIs introduces unique challenges, such as managing asynchronous operations, handling streaming data, and designing a responsive user experience for potentially slow or unreliable network requests. Angular [signals](guide/signals) and the [`resource`](guide/signals/resource) API provide powerful tools to solve these problems elegantly.

## [Triggering requests with signals](#triggering-requests-with-signals)

A common pattern when working with user-provided prompts is to separate the user's live input from the submitted value that triggers the API call.

1. Store the user's raw input in one signal as they type
2. When the user submits (e.g., by clicking a button), update a second signal with contents of the first signal.
3. Use the second signal in the **`params`** field of your [`resource`](/api/core/resource).

This setup ensures the resource's **`loader`** function only runs when the user explicitly submits their prompt, not on every keystroke. You can use additional signal parameters, like a `sessionId` or `userId` (which can be useful for creating persistent LLM sessions), in the `loader` field. This way, the request always uses these parameters' current values without re-triggering the asynchronous function defined in the `loader` field.

Many AI SDKs provide helper methods for making API calls. For example, the Genkit client library exposes a `runFlow` method for calling Genkit flows, which you can call from a resource's `loader`. For other APIs, you can use the [`httpResource`](guide/signals/resource#reactive-data-fetching-with-httpresource).

The following example shows a [`resource`](/api/core/resource) that fetches parts of an AI-generated story. The `loader` is triggered only when the `storyInput` signal changes.

```
// A resource that fetches three parts of an AI generated story
storyResource = resource({
  // The default value to use before the first request or on error
  defaultValue: DEFAULT_STORY,
  // The loader is re-triggered when this signal changes
  params: () => this.storyInput(),
  // The async function to fetch data
  loader: ({params}): Promise<StoryData> => {
    // The params value is the current value of the storyInput signal
    const url = this.endpoint();
    return runFlow({
      url,
      input: {
        userInput: params,
        sessionId: this.storyService.sessionId(), // Read from another signal
      },
    });
  },
});
```

## [Preparing LLM data for templates](#preparing-llm-data-for-templates)

You can configure LLM APIs to return structured data. Strongly typing your [`resource`](/api/core/resource) to match the expected output from the LLM provides better type safety and editor autocompletion.

To manage state derived from a resource, use a [`computed`](/api/core/computed) signal or [`linkedSignal`](/api/core/linkedSignal). Because [`linkedSignal`](/api/core/linkedSignal) [provides access to prior values](guide/signals/linked-signal), it can serve a variety of AI-related use cases, including

* building a chat history
* preserving or customizing data that templates display while LLMs generate content

In the example below, `storyParts` is a [`linkedSignal`](/api/core/linkedSignal) that appends the latest story parts returned from `storyResource` to the existing array of story parts.

```
storyParts = linkedSignal<string[], string[]>({
  // The source signal that triggers the computation
  source: () => this.storyResource.value().storyParts,
  // The computation function
  computation: (newStoryParts, previous) => {
    // Get the previous value of this linkedSignal, or an empty array
    const existingStoryParts = previous?.value || [];
    // Return a new array with the old and new parts
    return [...existingStoryParts, ...newStoryParts];
  },
});
```

## [Performance and user experience](#performance-and-user-experience)

LLM APIs may be slower and more error-prone than conventional, more deterministic APIs. You can use several Angular features to build a performant and user-friendly interface.

* **Scoped Loading:** place the [`resource`](/api/core/resource) in the component that directly uses the data. This helps limit change detection cycles (especially in zoneless applications) and prevents blocking other parts of your application. If data needs to be shared across multiple components, provide the [`resource`](/api/core/resource) from a service.
* **SSR and Hydration:** use Server-Side Rendering (SSR) with incremental hydration to render the initial page content quickly. You can show a placeholder for the AI-generated content and defer fetching the data until the component hydrates on the client.
* **Loading State:** use the [`resource`](/api/core/resource) `LOADING` [status](guide/signals/resource#resource-status) to show an indicator, like a spinner, while the request is in flight. This status covers both initial loads and reloads.
* **Error Handling and Retries:** use the [`resource`](/api/core/resource) [**`reload()`**](guide/signals/resource#reloading) method as a simple way for users to retry failed requests, may be more prevalent when relying on AI generated content.

The following example demonstrates how to create a responsive UI to dynamically display an AI generated image with loading and retry functionality.

```
<!-- Display a loading spinner while the LLM generates the image -->
@if (imgResource.isLoading()) {
  <div class="img-placeholder">
    <mat-spinner [diameter]="50" />
  </div>
  <!-- Dynamically populates the src attribute with the generated image URL -->
} @else if (imgResource.hasValue()) {
  <img [src]="imgResource.value()" />
  <!-- Provides a retry option if the request fails  -->
} @else {
  <div class="img-placeholder" (click)="imgResource.reload()">
    <mat-icon fontIcon="refresh" />
    <p>Failed to load image. Click to retry.</p>
  </div>
}
```

## [AI patterns in action: streaming chat responses](#ai-patterns-in-action-streaming-chat-responses)

Interfaces often display partial results from LLM-based APIs incrementally as response data arrives. Angular's resource API provides the ability to stream responses to support this type of pattern. The `stream` property of [`resource`](/api/core/resource) accepts an asynchronous function you can use to apply updates to a signal value over time. The signal being updated represents the data being streamed.

```
characters = resource({
  stream: async () => {
    const data = signal<ResourceStreamItem<string>>({value: ''});
    // Calls a Genkit streaming flow using the streamFlow method
    // exposed by the Genkit client SDK
    const response = streamFlow({
      url: '/streamCharacters',
      input: 10,
    });

    (async () => {
      for await (const chunk of response.stream) {
        data.update((prev) => {
          if ('value' in prev) {
            return {value: `${prev.value} ${chunk}`};
          } else {
            return {error: chunk as unknown as Error};
          }
        });
      }
    })();

    return data;
  },
});
```

The `characters` member is updated asynchronously and can be displayed in the template.

```
@if (characters.isLoading()) {
  <p>Loading...</p>
} @else if (characters.hasValue()) {
  <p>{{ characters.value() }}</p>
} @else {
  <p>{{ characters.error() }}</p>
}
```

On the server side, in `server.ts` for example, the defined endpoint sends the data to be streamed to the client. The following code uses Gemini with the Genkit framework but this technique is applicable to other APIs that support streaming responses from LLMs:

```
import {startFlowServer} from '@genkit-ai/express';
import {genkit} from 'genkit/beta';
import {googleAI, gemini20Flash} from '@genkit-ai/googleai';

const ai = genkit({plugins: [googleAI()]});

export const streamCharacters = ai.defineFlow(
  {
    name: 'streamCharacters',
    inputSchema: z.number(),
    outputSchema: z.string(),
    streamSchema: z.string(),
  },
  async (count, {sendChunk}) => {
    const {response, stream} = ai.generateStream({
      model: gemini20Flash,
      config: {
        temperature: 1,
      },
      prompt: `Generate ${count} different RPG game characters.`,
    });

    (async () => {
      for await (const chunk of stream) {
        sendChunk(chunk.content[0].text!);
      }
    })();

    return (await response).text;
  },
);

startFlowServer({
  flows: [streamCharacters],
});
```

----
url: https://angular.dev/api/core/ImportProvidersSource
----

## [API](/api/core/ImportProvidersSource#api)

```
    
      type ImportProvidersSource = | Type<unknown>  | ModuleWithProviders<unknown>  | Array<ImportProvidersSource>
    
    
```

Jump to details

----
url: https://angular.dev/api/common/provideNetlifyLoader
----

## [API](/api/common/provideNetlifyLoader#api)

```
function provideNetlifyLoader(path?: string | undefined): Provider[];
```

@parampath`string | undefined`

optional URL of the desired Netlify site. Defaults to the current site.

@returns`Provider[]`

Set of providers to configure the Netlify loader.

Jump to details

----
url: https://angular.dev/api/aria/accordion/AccordionPanel
----

```
    
      class AccordionPanel {}
    
    
```

A reference to the trigger element.

A global unique identifier for the panel.

Whether the accordion panel is visible. True if the associated trigger is expanded.

The content panel of an accordion item that is conditionally visible.

This directive is a container for the content that is shown or hidden. It should expose a template reference that will be used by the corresponding `ngAccordionTrigger`. The content within the panel should be provided using an `ng-template` with the `ngAccordionContent` directive so that the content is not rendered on the page until the trigger is expanded. It applies `role="region"` for accessibility and uses the `inert` attribute to hide its content from assistive technologies when not visible.

```
<div ngAccordionPanel #panel="ngAccordionPanel">
  <ng-template ngAccordionContent>
    <p>This content is lazily rendered and will be shown when the panel is expanded.</p>
  </ng-template>
</div>
```

Jump to details

----
url: https://angular.dev/api/aria/tabs/TAB_LIST
----

```
    
      const TAB_LIST: InjectionToken<TabList>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/KeyValueChangeRecord
----

```
    
      interface KeyValueChangeRecord<K, V> {}
    
    
```

Current key in the Map.

Current value for the key or `null` if removed.

Previous value for the key or `null` if added.

Jump to details

----
url: https://angular.dev/api/aria/tabs/TabPanel
----

```
    
      class TabPanel implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

A global unique identifier for the tab.

A local unique identifier for the tabpanel.

Whether the tab panel is visible.

@returns`void`

@returns`void`

## [Description](#description)

A TabPanel container for the resources of layered content associated with a tab.

The `ngTabPanel` directive holds the content for a specific tab. It is linked to an `ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be applied to remove it from the accessibility tree. Proper styling is required for visual hiding.

```
<div ngTabPanel value="myTabId">
  <ng-template ngTabContent>
    <!-- Content for the tab panel -->
  </ng-template>
</div>
```

Jump to details

----
url: https://angular.dev/api/forms/UntypedFormArray
----

```
    
      type UntypedFormArray = FormArray<any>
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/withIncrementalHydration
----

Deprecation warning

Since v22.0.0, incremental hydration is enabled by default with [`provideClientHydration`](/api/platform-browser/provideClientHydration). Intent to remove in v24.

## [API](#api)

```
function withIncrementalHydration(): HydrationFeature<HydrationFeatureKind.IncrementalHydration>;
```

@deprecated

Since v22.0.0, incremental hydration is enabled by default with [`provideClientHydration`](/api/platform-browser/provideClientHydration). Intent to remove in v24.

@returns`HydrationFeature<HydrationFeatureKind.IncrementalHydration>`

## [Usage Notes](#usage-notes)

Basic example of how you can enable incremental hydration in your application when the [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) function is used:

```
bootstrapApplication(App, {
  providers: [provideClientHydration(withIncrementalHydration())]
});
```

Jump to details

----
url: https://angular.dev/api/platform-server/renderApplication
----

```
function renderApplication(
  bootstrap: (context: BootstrapContext) => Promise<ApplicationRef>,
  options: {
    document?: string | Document | undefined;
    url?: string | undefined;
    platformProviders?: Provider[] | undefined;
    allowedHosts?: string[] | undefined;
  },
): Promise<string>;
```

@parambootstrap`(context: BootstrapContext) => Promise<ApplicationRef>`

A method that when invoked returns a promise that returns an [`ApplicationRef`](/api/core/ApplicationRef) instance once resolved. The method is invoked with an [`Injector`](/api/core/Injector) instance that provides access to the platform-level dependency injection context.

@paramoptions`{ document?: string | Document | undefined; url?: string | undefined; platformProviders?: Provider[] | undefined; allowedHosts?: string[] | undefined; }`

Additional configuration for the render operation:

* `document` - the document of the page to render, either as an HTML string or as a reference to the `document` instance.
* `url` - the URL for the current render request.
* `platformProviders` - the platform level providers for the current render request.
* `allowedHosts` - the allowed hosts list for host validation in server-side rendering.

@returns`Promise<string>`

A Promise, that returns serialized (to a string) rendered page, once resolved.

## [Usage Notes](#usage-notes)

```
import { BootstrapContext, bootstrapApplication } from '@angular/platform-browser';
import { renderApplication } from '@angular/platform-server';
import { ApplicationConfig } from '@angular/core';
import { AppComponent } from './app.component';

const appConfig: ApplicationConfig = { providers: [...] };
const bootstrap = (context: BootstrapContext) =>
  bootstrapApplication(AppComponent, config, context);
const output = await renderApplication(bootstrap);
```

Jump to details

----
url: https://angular.dev/best-practices/performance
----

Angular includes many optimizations out of the box, but as applications grow, you may need to fine-tune both how quickly your app loads and how responsive it feels during use. These guides cover the tools and techniques Angular provides to help you build fast applications.

## [Loading performance](#loading-performance)

Loading performance determines how quickly your application becomes visible and interactive. Slow loading directly impacts [Core Web Vitals](https://web.dev/vitals/) like Largest Contentful Paint (LCP) and Time to First Byte (TTFB).

| Technique                                                                                               | What it does                                                                                                                                                                                                                    | When to use it                                                                                |
| :------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- |
| [Lazy-loaded routes](best-practices/performance/lazy-loaded-routes#lazily-loaded-components-and-routes) | Defers loading route components until navigation, reducing the initial bundle size                                                                                                                                              | Applications with multiple routes where not all are needed on initial load                    |
| [Deferred loading with `@defer`](best-practices/performance/defer)                                      | Splits components into separate bundles that load on demand                                                                                                                                                                     | Components not visible on initial render, heavy third-party libraries, below-the-fold content |
| [Lazy loading services with `injectAsync`](guide/di/lazy-loading-services)                              | Splits rarely used services into separate chunks and loads them on demand                                                                                                                                                       | Services backed by large libraries or infrequently used features                              |
| [Image optimization](best-practices/performance/image-optimization)                                     | Prioritizes LCP images, lazy loads others, generates responsive `srcset` attributes                                                                                                                                             | Any application that displays images                                                          |
| [Server-side rendering](best-practices/performance/ssr)                                                 | Renders pages on the server for faster first paint and better SEO, with [hydration](guide/hydration) to restore interactivity and [incremental hydration](guide/incremental-hydration) to defer hydrating sections until needed | Content-heavy applications, pages that need search engine indexing                            |

## [Runtime performance](#runtime-performance)

Runtime performance determines how responsive your application feels after it loads. Angular's change detection system keeps the DOM in sync with your data, and optimizing how and when it runs is the primary lever for improving runtime performance.

| Technique                                                       | What it does                                                                                        | When to use it                                                                        |
| :-------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ |
| [Zoneless change detection](guide/zoneless)                     | Removes ZoneJS overhead and triggers change detection only when signals or events indicate a change | New applications (default in Angular v21+), or existing applications ready to migrate |
| [Slow computations](best-practices/slow-computations)           | Identifies and optimizes expensive template expressions and lifecycle hooks                         | Profiling reveals specific components causing slow change detection cycles            |
| [Skipping component subtrees](best-practices/skipping-subtrees) | Uses `OnPush` change detection to skip unchanged component trees                                    | Applications that need finer control over change detection                            |
| [Zone pollution](best-practices/zone-pollution)                 | Prevents unnecessary change detection caused by third-party libraries or timers                     | Zone-based applications where profiling reveals excessive change detection cycles     |

## [Measuring performance](#measuring-performance)

Identifying what to optimize is just as important as knowing how to optimize it. Angular integrates with browser developer tools to help you find bottlenecks.

| Tool                                                                       | What it does                                                                                                                                                                     |
| :------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Chrome DevTools profiling](best-practices/profiling-with-chrome-devtools) | Records Angular-specific performance data alongside browser profiling, with color-coded flame charts that show component rendering, change detection cycles, and lifecycle hooks |
| [Angular DevTools](tools/devtools)                                         | A browser extension that provides a component tree inspector and a profiler for visualizing change detection cycles                                                              |

## [What to optimize first](#what-to-optimize-first)

If you are unsure where to start, profile your application first using the [Chrome DevTools Angular track](best-practices/profiling-with-chrome-devtools) to identify specific bottlenecks.

As a general starting point:

* **Slow initial load** — Use [`@defer`](best-practices/performance/defer) to split large components out of the main bundle, [`NgOptimizedImage`](best-practices/performance/image-optimization) to prioritize above-the-fold images, and [server-side rendering](best-practices/performance/ssr) to deliver content faster.
* **Slow interactions after load** — Check whether [zoneless change detection](guide/zoneless) is enabled, look for [slow computations](best-practices/slow-computations) in templates or lifecycle hooks, and consider [`OnPush`](best-practices/skipping-subtrees) to reduce unnecessary change detection.

----
url: https://angular.dev/api/aria/listbox/guide/aria/autocomplete
----

# [Angular v22 is here!](events/v22)

[⚡ Explore what's new](events/v22)

search

Productivity\
Meets scalability
-----------------

### The framework for building scalable web apps with confidence

[](#learn-more)

Features that actually\
help you solve problems
-----------------------

Signals

Control Flow

Deferrable Views

Hydration

Enabling you to build\
smarter and faster
------------------

----
url: https://angular.dev/playground
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

main.ts

›

import {Component} from '@angular/core';

import {bootstrapApplication} from '@angular/platform-browser';



@Component({

selector: 'app-root',

template: \` Hello world! \`,

})

export class Playground {}



bootstrapApplication(Playground);



Preview

Booting

Console

Terminal

----
url: https://angular.dev/api/forms/signals/FormOptions
----

```
    
      interface FormOptions<TModel> {}
    
    
```

The injector to use for dependency injection. If this is not provided, the injector for the current [injection context](guide/di/dependency-injection-context), will be used.

The name of the root form, used in generating name attributes for the fields.

@experimental

Configuration options to expose this form as an experimental WebMCP AI agent tool.

Options that define how to handle form submission.

Jump to details

----
url: https://angular.dev/api/forms/signals/applyEach
----

## [API](/api/forms/signals/applyEach#api)

```
function applyEach<TValue extends ReadonlyArray<any>>(
  path: SchemaPath<TValue>,
  schema: NoInfer<SchemaOrSchemaFn<TValue[number], Item>>,
): void;
function applyEach<TValue extends Object>(
  path: SchemaPath<TValue>,
  schema: NoInfer<SchemaOrSchemaFn<ItemType<TValue>, Child>>,
): void;
```

Applies a schema to each item of an array.

@parampath`SchemaPath<TValue>`

The target path for an array field whose items the schema will be applied to.

@paramschema`NoInfer<SchemaOrSchemaFn<TValue[number], Item>>`

A schema for an element of the array, or function that binds logic to an element of the array.

@returns`void`

Usage notes

```
const nameSchema = schema<{first: string, last: string}>((name) => {
  required(name.first);
  required(name.last);
});
const namesForm = form(signal([{first: '', last: ''}]), (names) => {
  applyEach(names, nameSchema);
});
```

@parampath`SchemaPath<TValue>`

@paramschema`NoInfer<SchemaOrSchemaFn<ItemType<TValue>, Child>>`

@returns`void`

## [Usage Notes](/api/forms/signals/applyEach#usage-notes)

```
const nameSchema = schema<{first: string, last: string}>((name) => {
  required(name.first);
  required(name.last);
});
const namesForm = form(signal([{first: '', last: ''}]), (names) => {
  applyEach(names, nameSchema);
});
```

Jump to details

----
url: https://angular.dev/api/router/upgrade/setUpLocationSync
----

```
function setUpLocationSync(
  ngUpgrade: UpgradeModule,
  urlType?: 'path' | 'hash',
): void;
```

@paramngUpgrade`UpgradeModule`

The upgrade NgModule.

@paramurlType`"path" | "hash"`

The location strategy.

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/FactorySansProvider
----

```
    
      interface FactorySansProvider {}
    
    
```

A function to invoke to create a value for this `token`. The function is invoked with resolved values of `token`s in the `deps` field.

A list of `token`s to be resolved by the injector. The list of values is then used as arguments to the `useFactory` function.

Jump to details

----
url: https://angular.dev/api/aria/combobox/Combobox
----

* * [Combobox](/api/aria/combobox/Combobox)
  * [COMBOBOX\_POPUP](/api/aria/combobox/COMBOBOX_POPUP)
  * [ComboboxPopup](/api/aria/combobox/ComboboxPopup)
  * [ComboboxWidget](/api/aria/combobox/ComboboxWidget)

## [API](#api)

```
    
      class Combobox extends DeferredContentAware implements OnInit {}
    
    
```

A reference to the input element.

Whether the combobox is disabled.

Whether the combobox is readonly.

Whether the combobox is soft disabled (remains focusable).

Whether the combobox should always remain expanded.

The tabindex of the combobox.

Whether the combobox is expanded.

The value of the combobox input.

An inline suggestion to be displayed in the input.

@returns`void`

## [Description](#description)

A directive that coordinates a combobox trigger element and its associated popup widget.

The `ngCombobox` directive is applied directly to the interactive trigger element, which can be either an editable `<input>` (for search/autocomplete behaviors) or a non-editable element like a `<div>` (for custom select dropdowns). It manages focus and expansion states, coordinates autocomplete suggestions (if editable), and forwards navigation keys down into the active popup.

### [Example 1: Editable Autocomplete Input](#example-1-editable-autocomplete-input)

```
<input ngCombobox #combobox="ngCombobox" [(value)]="searchQuery" [(expanded)]="isExpanded" />

<ng-template ngComboboxPopup [combobox]="combobox">
  <div ngComboboxWidget #listbox="ngListbox" ngListbox [(value)]="selectedValues" [activeDescendant]="listbox.activeDescendant()">
    <div ngOption value="first">First Option</div>
    <div ngOption value="second">Second Option</div>
  </div>
</ng-template>
```

### [Example 2: Non-Editable Custom Select Dropdown](#example-2-non-editable-custom-select-dropdown)

```
<div ngCombobox #combobox="ngCombobox" [(expanded)]="isExpanded" class="select-trigger">
  {{selectedValue}}
</div>

<ng-template ngComboboxPopup [combobox]="combobox">
  <div ngComboboxWidget #listbox="ngListbox" ngListbox [(value)]="selectedValues" [activeDescendant]="listbox.activeDescendant()">
    <div ngOption value="first">First Option</div>
    <div ngOption value="second">Second Option</div>
  </div>
</ng-template>
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragRelease
----

```
    
      interface CdkDragRelease<T = any> {}
    
    
```

Draggable that emitted the event.

Native event that caused the release event.

Jump to details

----
url: https://angular.dev/api/core/ApplicationRef
----

```
    
      class ApplicationRef {
}
    
    
```

Indicates whether this instance was destroyed.

Get a list of component types registered to this application. This list is populated even before the component is created.

Get a list of components registered to this application.

Returns an Observable that indicates when the application is stable or unstable.

@returns`Promise<void>`

A promise that resolves when the application becomes stable

The [`EnvironmentInjector`](/api/core/EnvironmentInjector) used to create this application.

Bootstrap a component onto the element identified by its selector or, optionally, to a specified element.

@paramcomponent`Type<C>`

@paramoptions`{ hostElement?: string | Element | undefined; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[] | undefined; bindings?: Binding[] | undefined; } | undefined`

@returns`ComponentRef<C>`

Usage notes

### [Bootstrap process](#bootstrap-process)

When bootstrapping a component, Angular mounts it onto a target DOM element and kicks off automatic change detection. The target DOM element can be provided using the `hostElement` argument.

If the target DOM element is not provided, Angular tries to find one on a page using the `selector` of the component that is being bootstrapped (first matched element is used).

### [Example](#example)

Generally, we define the component to bootstrap in the `bootstrap` array of [`NgModule`](/api/core/NgModule), but it requires us to know the component while writing the application code.

Imagine a situation where we have to wait for an API call to decide about the component to bootstrap. We can use the `ngDoBootstrap` hook of the [`NgModule`](/api/core/NgModule) and call this method to dynamically bootstrap a component.

```
ngDoBootstrap(appRef: ApplicationRef) {
    this.fetchDataFromApi().then((componentName: string) => {
      if (componentName === 'ComponentOne') {
        appRef.bootstrap(ComponentOne);
      } else {
        appRef.bootstrap(ComponentTwo);
      }
    });
  }
```

Optionally, a component can be mounted onto a DOM element that does not match the selector of the bootstrapped component.

In the following example, we are providing a CSS selector to match the target element.

```
ngDoBootstrap(appRef: ApplicationRef) {
    appRef.bootstrap(ComponentThree, '#root-element');
  }
```

While in this example, we are providing reference to a DOM node.

```
ngDoBootstrap(appRef: ApplicationRef) {
    const element = document.querySelector('#root-element');
    appRef.bootstrap(ComponentFour, element!);
  }
```

@paramcomponent`Type<C>`

@paramhostElement`string | Element | undefined`

@returns`ComponentRef<C>`

Invoke this method to explicitly process change detection and its side-effects.

In development mode, `tick()` also performs a second change detection cycle to ensure that no further changes are detected. If additional changes are picked up during this second cycle, bindings in the app have side-effects that cannot be resolved in a single change detection pass. In this case, Angular throws an error, since an Angular application can only have one change detection pass during which all change detection must complete.

@returns`void`

Attaches a view so that it will be dirty checked. The view will be automatically detached when it is destroyed. This will throw if the view is already attached to a ViewContainer.

@paramviewRef`ViewRef`

@returns`void`

Detaches a view from dirty checking again.

@paramviewRef`ViewRef`

@returns`void`

Registers a listener to be called when an instance is destroyed.

@paramcallback`() => void`

A callback function to add as a listener.

@returns`VoidFunction`

A function which unregisters a listener.

Destroys an Angular application represented by this [`ApplicationRef`](/api/core/ApplicationRef). Calling this function will destroy the associated environment injectors as well as all the bootstrapped components with their views.

@returns`void`

Returns the number of attached views.

## [Usage Notes](#usage-notes)

### [isStable examples and caveats](#isstable-examples-and-caveats)

Note two important points about `isStable`, demonstrated in the examples below:

* the application will never be stable if you start any kind of recurrent asynchronous task when the application starts (for example for a polling process, started with a `setInterval`, a `setTimeout` or using RxJS operators like `interval`);
* the `isStable` Observable runs outside of the Angular zone.

Let's imagine that you start a recurrent task (here incrementing a counter, using RxJS `interval`), and at the same time subscribe to `isStable`.

```
constructor(appRef: ApplicationRef) {
  appRef.isStable.pipe(
     filter(stable => stable)
  ).subscribe(() => console.log('App is stable now');
  interval(1000).subscribe(counter => console.log(counter));
}
```

In this example, `isStable` will never emit `true`, and the trace "App is stable now" will never get logged.

If you want to execute something when the app is stable, you have to wait for the application to be stable before starting your polling process.

```
constructor(appRef: ApplicationRef) {
  appRef.isStable.pipe(
    first(stable => stable),
    tap(stable => console.log('App is stable now')),
    switchMap(() => interval(1000))
  ).subscribe(counter => console.log(counter));
}
```

In this example, the trace "App is stable now" will be logged and then the counter starts incrementing every second.

Note also that this Observable runs outside of the Angular zone, which means that the code in the subscription to this Observable will not trigger the change detection.

Let's imagine that instead of logging the counter value, you update a field of your component and display it in its template.

```
constructor(appRef: ApplicationRef) {
  appRef.isStable.pipe(
    first(stable => stable),
    switchMap(() => interval(1000))
  ).subscribe(counter => this.value = counter);
}
```

As the `isStable` Observable runs outside the zone, the `value` field will be updated properly, but the template will not be refreshed!

You'll have to manually trigger the change detection to update the template.

```
constructor(appRef: ApplicationRef, cd: ChangeDetectorRef) {
  appRef.isStable.pipe(
    first(stable => stable),
    switchMap(() => interval(1000))
  ).subscribe(counter => {
    this.value = counter;
    cd.detectChanges();
  });
}
```

Or make the subscription callback run inside the zone.

```
constructor(appRef: ApplicationRef, zone: NgZone) {
  appRef.isStable.pipe(
    first(stable => stable),
    switchMap(() => interval(1000))
  ).subscribe(counter => zone.run(() => this.value = counter));
}
```

Jump to details

----
url: https://angular.dev/api/core/ResourceLoader
----

```
    
      type ResourceLoader<T, R> = (param: ResourceLoaderParams<R>) => PromiseLike<T>
    
    
```

Jump to details

----
url: https://angular.dev/tutorials/first-app/13-search
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/12-forms)[](/tutorials/first-app/14-http)

[](/tutorials/first-app/12-forms)[](/tutorials/first-app/14-http)

This tutorial lesson demonstrates how to add a search functionality to your Angular app.

The app will enable users to search through the data provided by your app and display only the results that match the entered term.

[](https://www.youtube.com/watch?v=5K10oYJ5Y-E\&autoplay=1)

**IMPORTANT:** We recommend using your local environment for this step of the tutorial.

## [What you'll learn](#what-youll-learn)

* Your app will use data from a form to search for matching housing locations
* Your app will display only the matching housing locations

1. ### [Update the home component properties](#update-the-home-component-properties)

   In this step, you'll update the `Home` class to store data in a new array property that you will use for filtering.

   1. In `src/app/home/home.ts`, add new property to the class called `filteredLocationList`.

      ### Add the filteredLocationList property in home.ts

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
          this.filteredLocationList = this.housingLocationList;
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

      The `filteredLocationList` hold the values that match the search criteria entered by the user.

   2. The `filteredLocationList` should contain the total set of housing locations values by default when the page loads. Update the `constructor` for the `Home` to set the value.

      ### Set the value of filteredLocationList

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
          this.filteredLocationList = this.housingLocationList;
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

2. ### [Update the home component template](#update-the-home-component-template)

   The `Home` already contains an input field that you will use to capture input from the user. That string text will be used to filter the results.

   1. Update the `Home` template to include a template variable in the `input` element called `#filter`.

      ### Add a template variable to the input HTML element in home.ts

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
          this.filteredLocationList = this.housingLocationList;
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

      This example uses a [template reference variable](guide/templates) to get access to the `input` element as its value.

   2. Next, update the component template to attach an event handler to the "Search" button.

      ### Bind the button click event to a method in home.ts

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
          this.filteredLocationList = this.housingLocationList;
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

      By binding to the `click` event on the `button` element, you are able to call the `filterResults` function. The argument to the function is the `value` property of the `filter` template variable. Specifically, the `.value` property from the `input` HTML element.

   3. The last template update is to the `@for` directive. Update the `@for` to iterate over values from the `filteredLocationList` array.

      ### Update the @for template directive in home.ts

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
          this.filteredLocationList = this.housingLocationList;
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

3. ### [Implement the event handler function](#implement-the-event-handler-function)

   The template has been updated to bind the `filterResults` function to the `click` event. Next, your task is to implement the `filterResults` function in the `Home` class.

   1. Update the `Home` class to include the implementation of the `filterResults` function.

      ### Add the filterResults function implementation

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingLocationList = this.housingService.getAllHousingLocations();
          this.filteredLocationList = this.housingLocationList;
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

      This function uses the `Array` `filter` function to compare the value of the `text` parameter against the `housingLocation.city` property. You can update this function to match against any property or multiple properties for a fun exercise.

   2. Save your code.

   3. Refresh the browser and confirm that you can search the housing location data by city when you click the "Search" button after entering text.

**SUMMARY:** In this lesson, you updated your app to use template variables to interact with template values, and add search functionality using event binding and array functions.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/guide/routing/navigate-to-routes
----

The RouterLink directive is Angular's declarative approach to navigation. It allows you to use standard anchor elements (`<a>`) that seamlessly integrate with Angular's routing system.

## [How to use RouterLink](#how-to-use-routerlink)

Instead of using regular anchor elements `<a>` with an `href` attribute, you add a RouterLink directive with the appropriate path in order to leverage Angular routing.

```
import {RouterLink} from '@angular/router';

@Component({
  template: `
    <nav>
      <a routerLink="/user-profile">User profile</a>
      <a routerLink="/settings">Settings</a>
    </nav>
  `,
  imports: [RouterLink],
  ...
})
export class App {}
```

### [Using absolute or relative links](#using-absolute-or-relative-links)

**Relative URLs** in Angular routing allow you to define navigation paths relative to the current route's location. This is in contrast to **absolute URLs** which contain the full path with the protocol (e.g., `http://`) and the **root domain** (e.g., `google.com`).

```
<!-- Absolute URL -->
<a href="https://www.angular.dev/essentials">Angular Essentials Guide</a>

<!-- Relative URL -->
<a href="/essentials">Angular Essentials Guide</a>
```

In this example, the first example contains the full path with the protocol (i.e., `https://`) and the root domain (i.e., `angular.dev`) explicitly defined for the essentials page. In contrast, the second example assumes the user is already on the correct root domain for `/essentials`.

Generally speaking, relative URLs are preferred as they are more maintainable across applications since they don’t need to know their absolute position within the routing hierarchy.

### [How relative URLs work](#how-relative-urls-work)

Angular routing has two syntaxes for defining relative URLs: strings and arrays.

```
<!-- Navigates user to /dashboard -->
<a routerLink="dashboard">Dashboard</a>
<a [routerLink]="['dashboard']">Dashboard</a>
```

**HELPFUL:** Passing a string is the most common way to define relative URLs.

When you need to define dynamic parameters in a relative URL, use the array syntax:

```
<a [routerLink]="['user', currentUserId]">Current User</a>
```

In addition, Angular routing allows you to specify whether you want the path to be relative to the current URL or to the root domain based on whether the relative path is prefixed with a forward slash (`/`) or not.

For example, if the user is on `example.com/settings`, here is how different relative paths can be defined for various scenarios:

```
<!-- Navigates to /settings/notifications -->
<a routerLink="notifications">Notifications</a>
<a routerLink="/settings/notifications">Notifications</a>

<!-- Navigates to /team/:teamId/user/:userId -->
<a routerLink="/team/123/user/456">User 456</a>
<a [routerLink]="['/team', teamId, 'user', userId]">Current User</a>
```

## [Programmatic navigation to routes](#programmatic-navigation-to-routes)

While [`RouterLink`](/api/router/RouterLink) handles declarative navigation in templates, Angular provides programmatic navigation for scenarios where you need to navigate based on logic, user actions, or application state. By injecting [`Router`](/api/router/Router), you can dynamically navigate to routes, pass parameters, and control navigation behavior in your TypeScript code.

### [`router.navigate()`](#routernavigate)

You can use the `router.navigate()` method to programmatically navigate between routes by specifying a URL path array.

```
import {Router} from '@angular/router';

@Component({
  selector: 'app-dashboard',
  template: ` <button (click)="navigateToProfile()">View Profile</button> `,
})
export class AppDashboard {
  private router = inject(Router);

  navigateToProfile() {
    // Standard navigation
    this.router.navigate(['/profile']);

    // With route parameters
    this.router.navigate(['/users', userId]);

    // With query parameters
    this.router.navigate(['/search'], {
      queryParams: {category: 'books', sort: 'price'},
    });

    // With matrix parameters
    this.router.navigate(['/products', {featured: true, onSale: true}]);
  }
}
```

`router.navigate()` supports both simple and complex routing scenarios, allowing you to pass route parameters, [query parameters](/guide/routing/read-route-state#query-parameters), and control navigation behavior.

You can also build dynamic navigation paths relative to your component’s location in the routing tree using the `relativeTo` option.

```
import {Router, ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-user-detail',
  template: `
    <button (click)="navigateToEdit()">Edit User</button>
    <button (click)="navigateToParent()">Back to List</button>
  `,
})
export class UserDetail {
  private route = inject(ActivatedRoute);
  private router = inject(Router);

  // Navigate to a sibling route
  navigateToEdit() {
    // From: /users/123
    // To:   /users/123/edit
    this.router.navigate(['edit'], {relativeTo: this.route});
  }

  // Navigate to parent
  navigateToParent() {
    // From: /users/123
    // To:   /users
    this.router.navigate(['..'], {relativeTo: this.route});
  }

  navigateToList() {
    // Angular resolves the commands array as a single navigation path relative to the current route.
    // From: /users/123
    // Result: /users/list
    this.router.navigate(['..', 'list'], {relativeTo: this.route});
  }
}
```

When navigating multiple levels up, all `..` segments must be in the **first element** of the commands array. The router only parses `..` from the first command string — subsequent array elements are treated as literal path segments.

Prefer

```
// From: /team/123/users/456
// Result: /team/123/settings
this.router.navigate(['../../settings'], {relativeTo: this.route});
```

When using `relativeTo`, never prefix the first command with `/`. A leading `/` makes the navigation absolute and ignores `relativeTo` entirely.

Prefer

```
// From: /team/123/users/456
// Result: /team/123/users/456/edit
this.router.navigate(['edit'], {relativeTo: this.route});
```

Avoid

```
// From: /team/123/users/456
// Leading '/' causes absolute navigation — relativeTo is ignored
// Result: /edit
this.router.navigate(['/edit'], {relativeTo: this.route});
```

### [`router.navigateByUrl()`](#routernavigatebyurl)

The `router.navigateByUrl()` method provides a direct way to programmatically navigate using URL path strings rather than array segments. This method is ideal when you have a full URL path and need to perform absolute navigation, especially when working with externally provided URLs or deep linking scenarios.

```
// Standard route navigation
router.navigateByUrl('/products');

// Navigate to nested route
router.navigateByUrl('/products/featured');

// Complete URL with parameters and fragment
router.navigateByUrl('/products/123?view=details#reviews');

// Navigate with query parameters
router.navigateByUrl('/search?category=books&sortBy=price');

// With matrix parameters
router.navigateByUrl('/sales-awesome;isOffer=true;showModal=false');
```

In the event you need to replace the current URL in history, `navigateByUrl` also accepts a configuration object that has a `replaceUrl` option.

```
// Replace current URL in history
router.navigateByUrl('/checkout', {
  replaceUrl: true,
});
```

### [Display a different URL in the address bar](#display-a-different-url-in-the-address-bar)

You can pass a browserUrl option to navigateByUrl to display a different URL in the browser's address bar than the one used for route matching.

This is useful when you want to redirect a user to a different route—such as an error page—without changing the URL that the user originally tried to visit.

```
router.navigateByUrl('/not-found', {browserUrl: '/products/missing-item'});
```

Angular navigates to and renders the `/not-found` route, but the browser address bar shows `/products/missing-item`.

**NOTE:** `browserUrl` only affects what appears in the browser's address bar.

## [Customizing the browser URL with RouterLink](#customizing-the-browser-url-with-routerlink)

The [`RouterLink`](/api/router/RouterLink) directive also supports a `browserUrl` input, which lets you control the URL displayed in the browser's address bar when a link is clicked, independently of the route Angular navigates to.

```
<!-- Navigates to /dashboard, but the address bar shows /home -->
<a [routerLink]="['/dashboard']" [browserUrl]="'/home'">Go to Dashboard</a>
```

You can also bind a [`UrlTree`](/api/router/UrlTree) for more dynamic use cases:

```
import {Component, inject} from '@angular/core';
import {Router, RouterLink, UrlTree} from '@angular/router';

@Component({
  template: `
    <a [routerLink]="['/products', product.id]" [browserUrl]="displayUrl">
      {{ product.name }}
    </a>
  `,
  imports: [RouterLink],
})
export class ProductList {
  private router = inject(Router);

  product = {id: 42, name: 'Widget'};

  // Create a UrlTree to display in the address bar
  displayUrl: UrlTree = this.router.createUrlTree(['/products', 'widget']);
}
```

## [Next steps](#next-steps)

Learn how to [read route state](/guide/routing/read-route-state) to create responsive and context-aware components.

----
url: https://angular.dev/api/core/testing/TestModuleMetadata
----

```
    
      interface TestModuleMetadata {}
    
    
```

Whether NG0304 runtime errors should be thrown when unknown elements are present in component's template. Defaults to `false`, where the error is simply logged. If set to `true`, the error is thrown.

Whether errors should be thrown when unknown properties are present in component's template. Defaults to `false`, where the error is simply logged. If set to `true`, the error is thrown.

Whether errors that happen during application change detection should be rethrown.

When `true`, errors that are caught during application change detection will be reported to the `ErrorHandler` and rethrown to prevent them from going unnoticed in tests.

When `false`, errors are only forwarded to the `ErrorHandler`, which by default simply logs them to the console.

Defaults to `true`.

Whether defer blocks should behave with manual triggering or play through normally. Defaults to `manual`.

Whether to infer the tag name of test components from their selectors. Otherwise `div` will be used as the tag name for test components.

Whether animate.enter / animate.leave should trigger as normal or be disabled. Defaults to `false`.

Jump to details

----
url: https://angular.dev/api/forms/CheckboxControlValueAccessor
----

```
    
      class CheckboxControlValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor {}
    
    
```

## [Description](#description)

A [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing a value and listening to changes on a checkbox input element.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using a checkbox with a reactive form.](#using-a-checkbox-with-a-reactive-form)

The following example shows how to use a checkbox with a reactive form.

```
const rememberLoginControl = new FormControl();
```

```
<input type="checkbox" [formControl]="rememberLoginControl">
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MaxValidationError
----

```
    
      class MaxValidationError extends BaseNgValidationError {}
    
    
```

@parammax`number`

@paramoptions`ValidationErrorOptions | undefined`

@returns`MaxValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/core/OutputRefSubscription
----

```
    
      interface OutputRefSubscription {}
    
    
```

@returns`void`

## [Description](#description)

Function that can be used to manually clean up a programmatic [`OutputRef#subscribe`](/api/core/OutputRef#subscribe) subscription.

Note: Angular will automatically clean up subscriptions when the directive/component of the output is destroyed.

Jump to details

----
url: https://angular.dev/api/aria/tabs/Tab
----

```
    
      class Tab implements HasElement ,OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

A unique identifier for the widget.

Whether a tab is disabled.

The remote tabpanel unique identifier.

Whether the tab is active.

Whether the tab is selected.

Opens this tab panel.

@returns`void`

@returns`void`

@returns`void`

## [Description](#description)

A selectable tab in a TabList.

The `ngTab` directive represents an individual tab control within an `ngTabList`. It requires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`.

```
<li ngTab value="myTabId" [disabled]="isTabDisabled">
  My Tab Label
</li>
```

Jump to details

----
url: https://angular.dev/api/core/asNativeElements
----

```
function asNativeElements(debugEls: DebugElement[]): any;
```

@paramdebugEls`DebugElement[]`

@returns`any`

Jump to details

----
url: https://angular.dev/api/platform-server/testing/platformServerTesting
----

```
    
      const platformServerTesting: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/withHashLocation
----

```
function withHashLocation(): RouterHashLocationFeature;
```

@returns`RouterHashLocationFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can use the hash location option:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withHashLocation())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/tools/devtools
----

Angular DevTools is a browser extension that provides debugging and profiling capabilities for Angular applications.

Install Angular DevTools from the [Chrome Web Store](https://chrome.google.com/webstore/detail/angular-developer-tools/ienfalfjdbdpebioblfackkekamfmbnh) or from [Firefox Addons](https://addons.mozilla.org/firefox/addon/angular-devtools/).

You can open Chrome or Firefox DevTools on any web page by pressing `F12` or `Ctrl+Shift+I` (Windows or Linux) and `Fn+F12` or `Cmd+Option+I` (Mac). Once browser DevTools is open and Angular DevTools is installed, you can find it under the "Angular" tab.

**HELPFUL:** Chrome's new tab page does not run installed extensions, so the Angular tab will not appear in DevTools. Visit any other page to see it.

## [Open your application](/tools/devtools#open-your-application)

When you open the extension, you'll see four additional tabs:

| Tabs                                      | Details                                                                                                                |
| :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| [Components](tools/devtools/component)    | Lets you explore the components and directives in your application and preview or edit their state.                    |
| [Profiler](tools/devtools/profiler)       | Lets you profile your application and understand what the performance bottleneck is during change detection execution. |
| [Injector Tree](tools/devtools/injectors) | Lets you visualize the Environment and Element Injector hierarchy                                                      |
| [Router Tree](tools/devtools/router)      | Lets you visualize the routing tree of your application.                                                               |

Other tabs like `Transfer State` are experimental and can be enabled via the devtools settings and are not documented yet.

**HELPFUL:** For users of Chromium based browsers, you might be interested in the [Performance panel integration](/best-practices/profiling-with-chrome-devtools).

In the top-right corner of Angular DevTools you'll find the information button which open a popover. The information popover contains among others what version of Angular is running on the page as well as the devtools version.

### [Angular application not detected](/tools/devtools#angular-application-not-detected)

If you see an error message "Angular application not detected" when opening Angular DevTools, this means it is not able to communicate with an Angular app on the page. The most common reason for this is because the web page you are inspecting does not contain an Angular application. Double check that you are inspecting the right web page and that the Angular app is running.

### [We detected an application built with production configuration](/tools/devtools#we-detected-an-application-built-with-production-configuration)

If you see an error message "We detected an application built with production configuration. Angular DevTools only supports development builds.", this means that an Angular application was found on the page, but it was compiled with production optimizations. When compiling for production, Angular CLI removes various debug features to minimize the amount of the JavaScript on the page to improve performance. This includes features needed to communicate with DevTools.

To run DevTools, you need to compile your application with optimizations disabled. `ng serve` does this by default. If you need to debug a deployed application, disable optimizations in your build with the [`optimization` configuration option](reference/configs/workspace-config#optimization-configuration) (`{"optimization": false}`).

----
url: https://angular.dev/api/aria/tabs/TabList
----

```
    
      class TabList implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

Whether the tablist is vertically or horizontally oriented.

Text direction.

Whether focus should wrap when navigating.

Whether to allow disabled items to receive focus. When `true`, disabled items are focusable but not interactive. When `false`, disabled items are skipped during navigation.

The focus strategy used by the tablist.

* `roving`: Focus is moved to the active tab using `tabindex`.
* `activedescendant`: Focus remains on the tablist container, and `aria-activedescendant` is used to indicate the active tab.

The selection strategy used by the tablist.

* `follow`: The focused tab is automatically selected.
* `explicit`: Tabs are selected explicitly by the user (e.g., via click or spacebar).

The current selected tab as a model input.

Whether the tablist is disabled.

@returns`void`

@returns`void`

Opens the tab panel with the specified value.

@paramvalue`string`

@returns`boolean`

@paramvalue`string | undefined`

@returns`any`

## [Description](#description)

A TabList container.

The `ngTabList` directive controls a list of `ngTab` elements. It manages keyboard navigation, selection, and the overall orientation of the tabs. It should be placed within an `ngTabs` container.

```
<ul ngTabList [(selectedTab)]="mySelectedTab" orientation="horizontal" selectionMode="explicit">
  <li ngTab value="first">First Tab</li>
  <li ngTab value="second">Second Tab</li>
</ul>
```

Jump to details

----
url: https://angular.dev/api/core/AfterRenderRef
----

```
    
      interface AfterRenderRef {}
    
    
```

Shut down the callback, preventing it from being called again.

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/PreloadingStrategy
----

```
    
      abstract class PreloadingStrategy {}
    
    
```

@paramroute`Route`

@paramfn`() => Observable<any>`

@returns`Observable<any>`

Jump to details

----
url: https://angular.dev/guide/routing/loading-strategies
----

Understanding how and when routes and components load in Angular routing is crucial for building responsive web applications. Angular offers two primary strategies to control loading behavior:

1. **Eagerly loaded**: Routes and components that are loaded immediately
2. **Lazily loaded**: Routes and components loaded only when needed

Each approach offers distinct advantages for different scenarios.

## [Eagerly loaded components](#eagerly-loaded-components)

When you define a route with the [`component`](api/router/Route#component) property, the referenced component is eagerly loaded as part of the same JavaScript bundle as the route configuration.

```
import {Routes} from '@angular/router';
import {HomePage} from './components/home/home-page';
import {LoginPage} from './components/auth/login-page';

export const routes: Routes = [
  // HomePage and LoginPage are both directly referenced in this config,
  // so their code is eagerly included in the same JavaScript bundle as this file.
  {
    path: '',
    component: HomePage,
  },
  {
    path: 'login',
    component: LoginPage,
  },
];
```

Eagerly loading route components like this means that the browser has to download and parse all of the JavaScript for these components as part of your initial page load, but the components are available to Angular immediately.

While including more JavaScript in your initial page load leads to slower initial load times, this can lead to more seamless transitions as the user navigates through an application.

## [Lazily loaded components and routes](#lazily-loaded-components-and-routes)

You can use the [`loadComponent`](api/router/Route#loadComponent) property to lazily load the JavaScript for a component at the point at which that route would become active. The [`loadChildren`](api/router/Route#loadChildren) property lazily loads child routes during route matching.

```
import {Routes} from '@angular/router';

export const routes: Routes = [
  {
    path: 'login',
    loadComponent: () => import('./components/auth/login-page'),
  },
  {
    path: 'admin',
    loadComponent: () => import('./admin/admin.component'),
    loadChildren: () => import('./admin/admin.routes'),
  },
];
```

The [`loadComponent`](/api/router/Route#loadComponent) and [`loadChildren`](/api/router/Route#loadChildren) properties accept a loader function that returns a Promise that resolves to an Angular component or a set of routes respectively. In most cases, this function uses the standard [JavaScript dynamic import API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import). You can, however, use any arbitrary async loader function.

If the lazily loaded file uses a `default` export, you can return the `import()` promise directly without an additional `.then` call to select the exported class.

Lazily loading routes can significantly improve the load speed of your Angular application by removing large portions of JavaScript from the initial bundle. These portions of your code compile into separate JavaScript "chunks" that the router requests only when the user visits the corresponding route.

## [Injection context lazy loading](#injection-context-lazy-loading)

The Router executes [`loadComponent`](/api/router/Route#loadComponent) and [`loadChildren`](/api/router/Route#loadChildren) within the **injection context of the current route**, allowing you to call [`inject`](/api/core/inject)inside these loader functions to access providers declared on that route, inherited from parent routes through hierarchical dependency injection, or available globally. This enables context-aware lazy loading.

```
import {Routes} from '@angular/router';
import {inject} from '@angular/core';
import {FeatureFlags} from './feature-flags';

export const routes: Routes = [
  {
    path: 'dashboard',
    // Runs inside the route's injection context
    loadComponent: () => {
      const flags = inject(FeatureFlags);
      return flags.isPremium
        ? import('./dashboard/premium-dashboard')
        : import('./dashboard/basic-dashboard');
    },
  },
];
```

## [Should I use an eager or a lazy route?](#should-i-use-an-eager-or-a-lazy-route)

There are many factors to consider when deciding on whether a route should be eager or lazy.

In general, eager loading is recommended for primary landing page(s) while other pages would be lazy-loaded.

**NOTE:** While lazy routes have the upfront performance benefit of reducing the amount of initial data requested by the user, it adds future data requests that could be undesirable. This is particularly true when dealing with nested lazy loading at multiple levels, which can significantly impact performance.

## [Next steps](#next-steps)

Learn how to [display the contents of your routes with Outlets](/guide/routing/show-routes-with-outlets).

----
url: https://angular.dev/api/core/Provider
----

```
    
      type Provider = | TypeProvider  | ValueProvider  | ClassProvider  | ConstructorProvider  | ExistingProvider  | FactoryProvider  | any[]
    
    
```

Jump to details

----
url: https://angular.dev/api/core/contentChild
----

```
    
      
    
    
```

Initializes a content child query.

Consider using [`contentChild.required`](/api/core/contentChild) for queries that should always match.

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ descendants?: boolean | undefined; read?: undefined; debugName?: string | undefined; } | undefined`

@returns`Signal<LocatorT | undefined>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ descendants?: boolean | undefined; read: ProviderToken<ReadT>; debugName?: string | undefined; }`

@returns`Signal<ReadT | undefined>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ descendants?: boolean | undefined; read?: undefined; debugName?: string | undefined; } | undefined`

@returns`Signal<LocatorT>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ descendants?: boolean | undefined; read: ProviderToken<ReadT>; debugName?: string | undefined; }`

@returns`Signal<ReadT>`

## [Usage Notes](#usage-notes)

Create a child query in your component by declaring a class field and initializing it with the [`contentChild()`](/api/core/contentChild) function.

```
@Component({...})
export class Card {
  headerEl = contentChild<ElementRef>('h');                    // Signal<ElementRef|undefined>
  headerElElRequired = contentChild.required<ElementRef>('h'); // Signal<ElementRef>
  header = contentChild(MyHeader);                             // Signal<MyHeader|undefined>
  headerRequired = contentChild.required(MyHeader);            // Signal<MyHeader>
}
```

**NOTE:** By default `descendants` is `true` which means the query will traverse all descendants in the same template.

Jump to details

----
url: https://angular.dev/cli/analytics
----

* * [Overview](/cli/analytics)
  * [disable](/cli/analytics/disable)
  * [enable](/cli/analytics/enable)
  * [info](/cli/analytics/info)
  * [prompt](/cli/analytics/prompt)

```
ng analytics [options]
```

You can help the Angular Team to prioritize features and improvements by permitting the Angular team to send command-line command usage statistics to Google. The Angular Team does not collect usage statistics unless you explicitly opt in. When installing the Angular CLI you are prompted to allow global collection of usage statistics. If you say no or skip the prompt, no data is collected.

### [What is collected?](#what-is-collected)

Usage analytics include the commands and selected flags for each execution. Usage analytics may include the following information:

* Your operating system (macOS, Linux distribution, Windows) and its version.
* Package manager name and version (local version only).
* Node.js version (local version only).
* Angular CLI version (local version only).
* Command name that was run.
* Workspace information, the number of application and library projects.
* For schematics commands (add, generate and new), the schematic collection and name and a list of selected flags.
* For build commands (build, serve), the builder name, the number and size of bundles (initial and lazy), compilation units, the time it took to build and rebuild, and basic Angular-specific API usage.

Only Angular owned and developed schematics and builders are reported. Third-party schematics and builders do not send data to the Angular Team.

### Sub-commands

This command has the following sub-commands

* [disable](cli/analytics/disable)
* [enable](cli/analytics/enable)
* [info](cli/analytics/info)
* [prompt](cli/analytics/prompt)

## [Options](#options)

`help`

Shows a help message for this command in the console.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/guide/forms/signals/field-metadata
----

Field metadata is reactive data you can attach to an individual field. Angular's built-in constraint validators like `required()` and `min()` use this system internally. In other words, every time you call a validator, you're contributing to a metadata key for that particular field.

This guide covers the metadata system in depth: how reducers combine contributions from multiple schema rules, how to write custom reducers, how reading composes with `hasMetadata()`, and how managed metadata ties lifecycle-aware objects to individual fields.

## [You have already been using metadata](#you-have-already-been-using-metadata)

When you call `required()` in a schema and read `.required()` on the resulting field in a template, you are using the metadata system. `state.required` is not a special-case property. It is a convenience getter that returns the current value of a built-in [`REQUIRED`](/api/forms/signals/REQUIRED) metadata key.

```
import {Component, signal} from '@angular/core';
import {form, required, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <form>
      <label>
        Username
        @if (registrationForm.username().required()) {
          <span class="required-marker" aria-hidden="true">*</span>
        }
        <input [formField]="registrationForm.username" />
      </label>
    </form>
  `,
})
export class Registration {
  registrationModel = signal({username: ''});

  registrationForm = form(this.registrationModel, (path) => {
    required(path.username);
  });
}
```

Calling `required(path.username)` contributes a value to the [`REQUIRED`](/api/forms/signals/REQUIRED) metadata key on that field. Reading `registrationForm.username().required()` returns the accumulated value. The metadata key is the bridge connecting the two.

Several built-in constraint validators follow this pattern:

| Validator                                     | Metadata key                                                                          | Type                  | [`FieldState`](/api/forms/signals/FieldState) getter |
| --------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------- | ---------------------------------------------------- |
| `required()`                                  | [`REQUIRED`](/api/forms/signals/REQUIRED)                                             | `boolean`             | `required`                                           |
| `min()`                                       | [`MIN`](/api/forms/signals/MIN) selects [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER) | `number \| undefined` | `min`                                                |
| `max()`                                       | [`MAX`](/api/forms/signals/MAX) selects [`MAX_NUMBER`](/api/forms/signals/MAX_NUMBER) | `number \| undefined` | `max`                                                |
| [`minDate()`](/api/forms/signals/minDate)     | [`MIN`](/api/forms/signals/MIN) selects [`MIN_DATE`](/api/forms/signals/MIN_DATE)     | `Date \| undefined`   | `min`                                                |
| [`maxDate()`](/api/forms/signals/maxDate)     | [`MAX`](/api/forms/signals/MAX) selects [`MAX_DATE`](/api/forms/signals/MAX_DATE)     | `Date \| undefined`   | `max`                                                |
| [`minLength()`](/api/forms/signals/minLength) | [`MIN_LENGTH`](/api/forms/signals/MIN_LENGTH)                                         | `number \| undefined` | `minLength`                                          |
| [`maxLength()`](/api/forms/signals/maxLength) | [`MAX_LENGTH`](/api/forms/signals/MAX_LENGTH)                                         | `number \| undefined` | `maxLength`                                          |
| `pattern()`                                   | [`PATTERN`](/api/forms/signals/PATTERN)                                               | `RegExp[]`            | `pattern`                                            |

Non-constraint validators like `email()` and [`validate()`](/api/forms/signals/validate) do not contribute to metadata. They run their check and surface a validation error, but they do not publish a reactive value for templates to read.

## [When to use custom metadata](#when-to-use-custom-metadata)

When you need reactive data attached to a specific field that built-in state signals like `valid()`, [`disabled()`](/api/forms/signals/disabled), and `touched()` do not cover, use **custom metadata**.

Some examples might include:

* **Configuration attached to reusable field schemas.** A currency symbol on a price field, so any template or custom control rendering the field can display it. Or [`MIN_DATE`](/api/forms/signals/MIN_DATE) and [`MAX_DATE`](/api/forms/signals/MAX_DATE) on a date field, read by a reusable range picker.
* **Parsed values shared between rules on one field.** A phone number parsed once into E.164 format, so a format validator and a uniqueness check both read the same canonical form without reparsing.
* **Display hints assembled from the field's state.** A severity level (`'info' | 'warning' | 'error'`) that the UI maps to badges and icons, or a context-aware help message that changes based on what the user has typed and which other fields are filled in.

If you find yourself keeping a parallel `Map<fieldKey, value>` alongside your form to track something per field, that is a sign metadata is the right tool. Metadata stays colocated with the schema, stays reactive, and participates in the field's lifecycle.

## [Creating a metadata key](#creating-a-metadata-key)

When you want to create a custom key, call `createMetadataKey<TWrite>()`. The type parameter describes the value your schema rules will contribute.

```
import {createMetadataKey} from '@angular/forms/signals';

export const USERNAME_HELP = createMetadataKey<string>();
```

Every [`createMetadataKey()`](/api/forms/signals/createMetadataKey) call creates a new unique key. Two calls with matching type parameters are still two distinct keys, so define each key once at module scope and import it wherever it's needed.

**NOTE:** A key created without a reducer uses "override" semantics by default: the last contribution wins if multiple rules set the key.

## [Setting values from a schema](#setting-values-from-a-schema)

When you need to register a value for the key on a specific field, use `metadata(path, key, logic)` inside a schema function.

```
import {Component, computed, signal} from '@angular/core';
import {form, metadata, FormField} from '@angular/forms/signals';
import {USERNAME_HELP} from './metadata-keys';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <form>
      <label>
        Username
        <input [formField]="registrationForm.username" />
      </label>
      <p class="help">{{ usernameHelp() }}</p>
    </form>
  `,
})
export class Registration {
  registrationModel = signal({username: ''});

  registrationForm = form(this.registrationModel, (path) => {
    metadata(path.username, USERNAME_HELP, ({value}) => {
      const username = value();
      if (username.length === 0) {
        return 'Choose a unique username between 3 and 20 characters.';
      }
      if (username.length < 3) {
        return 'Keep typing, usernames are at least 3 characters.';
      }
      if (username.length > 20) {
        return 'Usernames are at most 20 characters.';
      }
      return 'Looks good.';
    });
  });

  usernameHelp = computed(() => this.registrationForm.username().metadata(USERNAME_HELP)?.() ?? '');
}
```

The logic function receives the field's context, which exposes `value` as a signal of the field's current value, `state` as the field's [`FieldState`](/api/forms/signals/FieldState), and methods like `valueOf(path)` and `stateOf(path)` for reading other fields in the same form. Any signal the function reads becomes a reactive dependency: when `value()` changes, the metadata recomputes, and any template reading the key updates.

## [Reading metadata from a field](#reading-metadata-from-a-field)

`hasMetadata(key)` returns `true` if any schema rule registered the key on this field. `state.metadata(key)` returns `undefined` when no rule has registered the key, and a signal of the current reduced value otherwise.

```
registrationForm.username().hasMetadata(USERNAME_HELP); // true if any metadata() rule registered this key
```

The shape of that inner value (whether it can itself be `undefined`, what type it holds) depends on the key's reducer. Reducers are covered in the next section.

When the key may not be registered, gate the read with `hasMetadata()`:

```
@if (registrationForm.username().hasMetadata(USERNAME_HELP)) {
  <p class="help">{{ registrationForm.username().metadata(USERNAME_HELP)!() }}</p>
}
```

When you know a rule always registers the key (because the schema in the same file does so), you can skip the `hasMetadata()` check and use optional chaining as a compact alternative:

```
const message = registrationForm.username().metadata(USERNAME_HELP)?.();
// message: string | undefined
```

Or, when the rule is guaranteed to have registered, drop the optional chain and assert:

```
const message = registrationForm.username().metadata(USERNAME_HELP)!();
// message: string | undefined (still, because the inner value may be undefined)
```

The component example above uses optional chaining inside a [`computed()`](/api/core/computed) so the template binds to a plain `string`, with an empty fallback for the initial frame.

This is the whole API for a single contributor. The next section covers what happens when more than one schema rule contributes to the same key, and how to combine those contributions with reducers.

## [Combining contributions with reducers](#combining-contributions-with-reducers)

Override semantics work when only one rule contributes to a key on a given field. As soon as two rules contribute, the first value is silently discarded:

```
const HELP = createMetadataKey<string>();

form(model, (path) => {
  metadata(path.username, HELP, () => 'Choose something unique across the system.');
  metadata(path.username, HELP, () => 'Usernames are 3 to 20 characters.');
});
```

After both rules run, `state.metadata(HELP)!()` returns only the second message. This is almost never what you want. Contributions often come from different sources: two schemas composed with [`apply()`](/api/forms/signals/apply) that each attach help text, or multiple validation rules that each contribute a hint.

To combine contributions, pass a reducer to [`createMetadataKey()`](/api/forms/signals/createMetadataKey). A reducer describes how to fold individual values into an accumulated result:

```
import {createMetadataKey, MetadataReducer} from '@angular/forms/signals';

const HELP = createMetadataKey<string, string[]>(MetadataReducer.list());

form(model, (path) => {
  metadata(path.username, HELP, () => 'Choose something unique across the system.');
  metadata(path.username, HELP, () => 'Usernames are 3 to 20 characters.');
});

// state.metadata(HELP)!() === [
//   'Choose something unique across the system.',
//   'Usernames are 3 to 20 characters.',
// ]
```

Notice the two type parameters on `createMetadataKey<TWrite, TAcc>`: the first is the type each rule contributes, the second is the type the reducer produces. With `list()`, rules contribute a `string` and the field reads back a `string[]`.

### [Built-in reducers](#built-in-reducers)

Angular provides six built-in reducers on the [`MetadataReducer`](api/forms/signals/MetadataReducer) namespace. `override()` has two forms with slightly different semantics, listed separately in the table:

| Reducer        | Accumulator type      | What it does                                                           | Initial value |
| -------------- | --------------------- | ---------------------------------------------------------------------- | ------------- |
| `list<T>()`    | `T[]`                 | Accepts `T \| undefined` contributions; appends non-`undefined` values | `[]`          |
| `or()`         | `boolean`             | `true` if any contribution is `true`                                   | `false`       |
| `and()`        | `boolean`             | `true` only if every contribution is `true`                            | `true`        |
| `min()`        | `number \| undefined` | Keeps the smallest contributed number                                  | `undefined`   |
| `max()`        | `number \| undefined` | Keeps the largest contributed number                                   | `undefined`   |
| `override()`   | `T \| undefined`      | Last contribution replaces previous (the default)                      | `undefined`   |
| `override(fn)` | `T`                   | Same, but with a provided initial value                                | `fn()`        |

`list()` is the only built-in reducer whose item type is wider than its accumulator's element type. A rule may contribute `undefined` and the reducer will silently drop it. This is how the built-in [`PATTERN`](/api/forms/signals/PATTERN) key handles dynamic `pattern()` rules whose logic function returns `undefined`: the `undefined` contribution is skipped rather than included in the final regex list.

### [How built-in validator keys use reducers](#how-built-in-validator-keys-use-reducers)

While [`MetadataReducer.min()`](/api/forms/signals/MetadataReducer#min) and [`MetadataReducer.max()`](/api/forms/signals/MetadataReducer#max) are reducers, you may be surprised to learn that they are not validators. [`MetadataReducer.min()`](/api/forms/signals/MetadataReducer#min) picks the smallest contribution to a key, while the `min()` validator enforces a lower bound on a field's value. They share a name but solve different problems.

The built-in constraint keys pick their reducers based on what "strictest" means for the constraint, which is often the opposite of what the key's name suggests:

| Key                                           | Reducer          | Reasoning                                                                                                                              |
| --------------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [`REQUIRED`](/api/forms/signals/REQUIRED)     | `or()`           | If any `required()` rule evaluates to `true`, the field is required.                                                                   |
| [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER) | `max()`          | A minimum-number constraint is strictest when largest. If one rule requires `>= 5` and another `>= 10`, the effective minimum is `10`. |
| [`MIN_DATE`](/api/forms/signals/MIN_DATE)     | `max()`          | Same logic as [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER): the latest required date wins.                                            |
| [`MAX_NUMBER`](/api/forms/signals/MAX_NUMBER) | `min()`          | A maximum-number constraint is strictest when smallest. If one rule caps at `100` and another at `50`, the effective maximum is `50`.  |
| [`MAX_DATE`](/api/forms/signals/MAX_DATE)     | `min()`          | Same logic as [`MAX_NUMBER`](/api/forms/signals/MAX_NUMBER): the earliest allowed date wins.                                           |
| [`MIN_LENGTH`](/api/forms/signals/MIN_LENGTH) | `max()`          | Same logic as [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER): the longest required length wins.                                         |
| [`MAX_LENGTH`](/api/forms/signals/MAX_LENGTH) | `min()`          | Same logic as [`MAX_NUMBER`](/api/forms/signals/MAX_NUMBER): the shortest allowed length wins.                                         |
| [`PATTERN`](/api/forms/signals/PATTERN)       | `list<RegExp>()` | Each `pattern()` call contributes a regex; the value must match all of them.                                                           |

[`MIN`](/api/forms/signals/MIN) and [`MAX`](/api/forms/signals/MAX) are selection keys. They point to the concrete key that matches the field's value type, such as [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER) for `min()` and [`MIN_DATE`](/api/forms/signals/MIN_DATE) for [`minDate()`](/api/forms/signals/minDate). This is why `field().min()` and `field().max()` work for both numeric and date fields.

This pairing of "strictest wins" is why calling `min(path.age, 18)` and `min(path.age, 21)` in two composed schemas works correctly. Each call registers its own validator that enforces its specific bound (so a value below either bound fails validation). Separately, each call contributes to the [`MIN_NUMBER`](/api/forms/signals/MIN_NUMBER) key, and `state.min!()` reports the aggregate (`21`) so UI and custom controls can read the effective minimum.

### [Writing a custom reducer](#writing-a-custom-reducer)

When you want to write your own reducer, implement an object matching the `MetadataReducer<TAcc, TItem>` interface:

```
interface MetadataReducer<TAcc, TItem> {
  reduce: (acc: TAcc, item: TItem) => TAcc;
  getInitial: () => TAcc;
}
```

You can define a custom reducer when none of the built-ins match the semantics you need. For example, a `SEVERITY` key that keeps the most severe level contributed by any rule:

```
import {createMetadataKey, type MetadataReducer} from '@angular/forms/signals';

type Severity = 'info' | 'warning' | 'error';

const SEVERITY_RANK: Record<Severity, number> = {info: 0, warning: 1, error: 2};

const maxSeverity: MetadataReducer<Severity | undefined, Severity> = {
  reduce(acc, item) {
    if (acc === undefined) return item;
    return SEVERITY_RANK[item] > SEVERITY_RANK[acc] ? item : acc;
  },
  getInitial: () => undefined,
};

export const SEVERITY = createMetadataKey<Severity, Severity | undefined>(maxSeverity);
```

Any number of rules can now contribute a severity, and the field reports the highest:

```
form(model, (path) => {
  metadata(path.password, SEVERITY, () => 'info');
  metadata(path.password, SEVERITY, ({value}) => (value().length < 12 ? 'warning' : 'info'));
  metadata(path.password, SEVERITY, ({value}) =>
    /password|1234/i.test(value()) ? 'error' : 'info',
  );
});
```

The reducer runs whenever any contribution's signals change, so `state.metadata(SEVERITY)!()` stays in sync with the current worst case across all rules.

**TIP:** Keep your reducers pure: `reduce()` should depend only on its two arguments, and `getInitial()` should return the same value every time it is called. Reducers run inside a reactive computation that re-executes when any contribution's signals change, so impure reducers produce inconsistent metadata.

## [Attaching lifecycle-aware objects with managed metadata](#attaching-lifecycle-aware-objects-with-managed-metadata)

Managed metadata stores a lifecycle-aware object on a field instead of a reactive value. Use it for per-field objects like a [`resource()`](/api/core/resource) that fetches external data, an [`effect()`](/api/core/effect) that syncs to an outside system, or a service handle scoped to a single field.

### [Creating a managed key](#creating-a-managed-key)

When you want to define a managed key, call `createManagedMetadataKey<TRead, TWrite>(create)`. The `create` function you pass produces the value the key holds.

```
import {Signal} from '@angular/core';
import {httpResource} from '@angular/common/http';
import {createManagedMetadataKey} from '@angular/forms/signals';

export interface UrlPreview {
  title: string;
  description?: string;
  image?: string;
}

export const URL_PREVIEW = createManagedMetadataKey((_state, url: Signal<string | undefined>) => {
  return httpResource<UrlPreview>(() => {
    const currentUrl = url();
    return currentUrl ? {url: '/api/url-preview', params: {url: currentUrl}} : undefined;
  });
});
```

The `create` function receives the field's [`FieldState`](/api/forms/signals/FieldState) and a `Signal<TAcc>` of data contributed by [`metadata()`](/api/forms/signals/metadata) rules for this key, and returns whatever object should live on the field. The return value is stored as-is: unlike non-managed keys, the framework does not wrap it in a [`computed()`](/api/core/computed).

`create` runs once when a field is constructed, inside the field's injection context. That lets you call `inject()`, [`resource()`](/api/core/resource), and [`effect()`](/api/core/effect) inside `create`, and ties cleanup to the field's lifecycle: when the field is destroyed, Angular destroys the injection context, and any [`resource()`](/api/core/resource), [`effect()`](/api/core/effect), or [`DestroyRef`](/api/core/DestroyRef) callback you registered there cleans up automatically.

Because `create` itself is not reactive, any behavior that needs to respond to signal changes has to live inside an [`effect()`](/api/core/effect), [`resource()`](/api/core/resource), or [`httpResource()`](/api/common/http/httpResource) set up during that initial call. `URL_PREVIEW` demonstrates the pattern: the [`httpResource()`](/api/common/http/httpResource) reads the URL signal inside its request function, so the request re-runs whenever the signal changes. The schema rule (`metadata(path.url, URL_PREVIEW, ({value}) => value())`) decides what data to feed in; the managed key decides what to do with it.

### [Using a managed key in a form](#using-a-managed-key-in-a-form)

When you need to use a managed key in a form, register a [`metadata()`](/api/forms/signals/metadata) rule for the key, and then read the returned object from the field state.

```
import {Component, computed, signal} from '@angular/core';
import {applyEach, form, metadata, FormField} from '@angular/forms/signals';
import {URL_PREVIEW} from './url-preview';

@Component({
  selector: 'app-link-editor',
  imports: [FormField],
  template: `
    <form>
      @for (link of linksForm.links; track link) {
        <fieldset>
          <label>
            URL
            <input [formField]="link.url" />
          </label>
          <!-- Read the URL_PREVIEW key for this link's url field; the result is the resource its create function produced -->
          @let preview = link.url().metadata(URL_PREVIEW);
          @if (preview?.isLoading()) {
            <p>Loading preview...</p>
          } @else if (preview?.hasValue() && preview.value(); as data) {
            <article class="preview">
              <h3>{{ data.title }}</h3>
              @if (data.description) {
                <p>{{ data.description }}</p>
              }
            </article>
          } @else if (preview?.error()) {
            <p class="error">Could not load preview.</p>
          }
        </fieldset>
      }
      <button type="button" (click)="addLink()">Add link</button>
    </form>
  `,
})
export class LinkEditor {
  URL_PREVIEW = URL_PREVIEW;

  linksModel = signal({links: [{url: ''}]});

  linksForm = form(this.linksModel, (path) => {
    // Register the URL_PREVIEW key on each link's url field.
    // applyEach runs the schema per item, so create() runs once per link
    // and each link gets its own resource.
    applyEach(path.links, (itemPath) => {
      metadata(itemPath.url, URL_PREVIEW, ({value}) => value());
    });
  });

  addLink() {
    this.linksForm.links().value.update((links) => [...links, {url: ''}]);
  }
}
```

Each array item gets its own `URL_PREVIEW` resource because [`applyEach`](/api/forms/signals/applyEach) registers the schema rules against each item independently. When the user adds a link, `create` runs for the new item's field. When a link is removed (not shown here, but a common pattern), the framework tears down that field's injector along with the resource.

## [Next steps](#next-steps)

Remember that metadata exists so reactive data can travel with the field through schema composition, accumulate across rules, and tear down with the field's lifecycle. It leverages the same system Angular's built-in validators use, and can be tailored to your own use cases.

For detailed API documentation, see:

* [`createMetadataKey()`](api/forms/signals/createMetadataKey) - Define a metadata key with optional reducer
* [`createManagedMetadataKey()`](api/forms/signals/createManagedMetadataKey) - Define a lifecycle-aware metadata key
* [`metadata()`](api/forms/signals/metadata) - Contribute a value to a metadata key in a schema
* [`MetadataReducer`](api/forms/signals/MetadataReducer) - Built-in reducers for combining contributions

For additional related guides on Signal Forms, check out:

----
url: https://angular.dev/tutorials/learn-angular/3-composing-components
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/2-updating-the-component-class)[](/tutorials/learn-angular/4-control-flow-if)

[](/tutorials/learn-angular/2-updating-the-component-class)[](/tutorials/learn-angular/4-control-flow-if)

You've learned to update the component template, component logic, and component styles, but how do you use a component in your application?

The `selector` property of the component configuration gives you a name to use when referencing the component in another template. You use the `selector` like an HTML tag, for example `app-user` would be `<app-user />` in the template.

**NOTE:** Learn more about [using components in the essentials guide](/essentials/components#using-components).

In this activity, you'll learn how to compose components.

***

In this example, there are two components `User` and `App`.

1. ### [Add a reference to `User`](#add-a-reference-to-user)

   Update the `App` template to include a reference to the `User` which uses the selector `app-user`. Be sure to add `User` to the imports array of `App`, this makes it available for use in the `App` template.

   ```
   template: `<app-user />`, imports: [User]
   ```

   The component now displays the message `Username: youngTech`. You can update the template code to include more markup.

2. ### [Add more markup](#add-more-markup)

   Because you can use any HTML markup that you want in a template, try updating the template for `App` to also include more HTML elements. This example will add a `<section>` element as the parent of the `<app-user>` element.

   ```
   template: `
   <section><app-user /></section>
   `,
   ```

You can use as much HTML markup and as many components as you need to bring your app idea to reality. You can even have multiple copies of your component on the same page.

That's a great segue, how would you conditionally show a component based on data? Head to the next section to find out.

----
url: https://angular.dev/api/forms/FormControl
----

## [API](/api/forms/FormControl#api)

```
    
      interface FormControl<TValue = any> extends AbstractControl<TValue> {
}
    
    
```

The default value of this FormControl, used whenever the control is reset without an explicit value. See [`FormControlOptions#nonNullable`](/api/forms/FormControlOptions#nonNullable) for more information on configuring a default value.

Sets a new value for the form control.

@paramvalue`TValue`

The new value for the control.

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; emitModelToViewChange?: boolean | undefined; emitViewToModelChange?: boolean | undefined; } | undefined`

Configuration options that determine how the control propagates changes and emits events when the value changes. The configuration options are passed to the [`* updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control value is updated. When false, no events are emitted.
* `emitModelToViewChange`: When true or not supplied (the default), each change triggers an `onChange` event to update the view.
* `emitViewToModelChange`: When true or not supplied (the default), each change triggers an `ngModelChange` event to update the model.

@returns`void`

Patches the value of a control.

This function is functionally the same as [`setValue`](/api/forms/FormControl#setValue) at this level. It exists for symmetry with [`patchValue`](/api/forms/FormGroup#patchValue) on `FormGroups` and `FormArrays`, where it does behave differently.

@paramvalue`TValue`

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; emitModelToViewChange?: boolean | undefined; emitViewToModelChange?: boolean | undefined; } | undefined`

@returns`void`

Resets the form control, marking it `pristine` and `untouched`, and resetting the value. The new value will be the provided value (if passed), `null`, or the initial value if `nonNullable` was set in the constructor via [`FormControlOptions`](/api/forms/FormControlOptions).

```
// By default, the control will reset to null.
const dog = new FormControl('spot');
dog.reset(); // dog.value is null

// If this flag is set, the control will instead reset to the initial value.
const cat = new FormControl('tabby', {nonNullable: true});
cat.reset(); // cat.value is "tabby"

// A value passed to reset always takes precedence.
const fish = new FormControl('finn', {nonNullable: true});
fish.reset('bubble'); // fish.value is "bubble"
```

@paramformState`TValue | FormControlState<TValue> | undefined`

Resets the control with an initial value, or an object that defines the initial value and disabled state.

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; overwriteDefaultValue?: boolean | undefined; } | undefined`

Configuration options that determine how the control propagates changes and emits events after the value changes.

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is reset. When false, no events are emitted.
* `overwriteDefaultValue`: When true, the value used to reset the control becomes the new default value of the control.

@returns`void`

For a simple FormControl, the raw value is equivalent to the value.

@returns`TValue`

Register a listener for change events.

@paramfn`Function`

The method that is called when the value changes

@returns`void`

Register a listener for disabled events.

@paramfn`(isDisabled: boolean) => void`

The method that is called when the disabled status changes.

@returns`void`

```
// Reference to the RequiredValidator
const ctrl = new FormControl<string | null>('', Validators.required);
ctrl.removeValidators(Validators.required);

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<string | null>('', minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)

ctrl.removeValidators(minValidator);
```

```
// Reference to the RequiredValidator
const ctrl = new FormControl<number | null>(0, Validators.required);
expect(ctrl.hasValidator(Validators.required)).toEqual(true)

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<number | null>(0, minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)
```

### [Manually set the errors for a control](/api/forms/FormControl#manually-set-the-errors-for-a-control)

```
const login = new FormControl('someLogin');
login.setErrors({
  notUnique: true
});

expect(login.valid).toEqual(false);
expect(login.errors).toEqual({ notUnique: true });

login.setValue('someOtherLogin');

expect(login.valid).toEqual(true);
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

If no path is given, this method checks for the error on the current control.

Retrieves the top-level ancestor of this control.

## [Description](/api/forms/FormControl#description)

Tracks the value and validation status of an individual form control.

This is one of the four fundamental building blocks of Angular forms, along with [`FormGroup`](/api/forms/FormGroup), [`FormArray`](/api/forms/FormArray) and [`FormRecord`](/api/forms/FormRecord). It extends the [`AbstractControl`](/api/forms/AbstractControl) class that implements most of the base functionality for accessing the value, validation status, user interactions and events.

[`FormControl`](/api/forms/FormControl) takes a single generic argument, which describes the type of its value. This argument always implicitly includes `null` because the control can be reset. To change this behavior, set `nonNullable` or see the usage notes below.

See [usage examples below](/api/forms/FormControl#usage-notes).

## [Usage Notes](/api/forms/FormControl#usage-notes)

### [Initializing Form Controls](/api/forms/FormControl#initializing-form-controls)

Instantiate a [`FormControl`](/api/forms/FormControl), with an initial value.

```
const control = new FormControl('some value');
console.log(control.value);     // 'some value'
```

The following example initializes the control with a form state object. The `value` and `disabled` keys are required in this case.

```
const control = new FormControl({ value: 'n/a', disabled: true });
console.log(control.value);     // 'n/a'
console.log(control.status);    // 'DISABLED'
```

The following example initializes the control with a synchronous validator.

```
const control = new FormControl('', Validators.required);
console.log(control.value);      // ''
console.log(control.status);     // 'INVALID'
```

The following example initializes the control using an options object.

```
const control = new FormControl('', {
   validators: Validators.required,
   asyncValidators: myAsyncValidator
});
```

### [The single type argument](/api/forms/FormControl#the-single-type-argument)

[`FormControl`](/api/forms/FormControl) accepts a generic argument, which describes the type of its value. In most cases, this argument will be inferred.

If you are initializing the control to `null`, or you otherwise wish to provide a wider type, you may specify the argument explicitly:

```
let fc = new FormControl<string|null>(null);
fc.setValue('foo');
```

You might notice that `null` is always added to the type of the control. This is because the control will become `null` if you call `reset`. You can change this behavior by setting `{nonNullable: true}`.

### [Configure the control to update on a blur event](/api/forms/FormControl#configure-the-control-to-update-on-a-blur-event)

Set the `updateOn` option to `'blur'` to update on the blur `event`.

```
const control = new FormControl('', { updateOn: 'blur' });
```

### [Configure the control to update on a submit event](/api/forms/FormControl#configure-the-control-to-update-on-a-submit-event)

Set the `updateOn` option to `'submit'` to update on a submit `event`.

```
const control = new FormControl('', { updateOn: 'submit' });
```

### [Reset the control back to a specific value](/api/forms/FormControl#reset-the-control-back-to-a-specific-value)

You reset to a specific form state by passing through a standalone value or a form state object that contains both a value and a disabled state (these are the only two properties that cannot be calculated).

```
const control = new FormControl('Nancy');

console.log(control.value); // 'Nancy'

control.reset('Drew');

console.log(control.value); // 'Drew'
```

### [Reset the control to its initial value](/api/forms/FormControl#reset-the-control-to-its-initial-value)

If you wish to always reset the control to its initial value (instead of null), you can pass the `nonNullable` option:

```
const control = new FormControl('Nancy', {nonNullable: true});

console.log(control.value); // 'Nancy'

control.reset();

console.log(control.value); // 'Nancy'
```

### [Reset the control back to an initial value and disabled](/api/forms/FormControl#reset-the-control-back-to-an-initial-value-and-disabled)

```
const control = new FormControl('Nancy');

console.log(control.value); // 'Nancy'
console.log(control.status); // 'VALID'

control.reset({ value: 'Drew', disabled: true });

console.log(control.value); // 'Drew'
console.log(control.status); // 'DISABLED'
```

Jump to details

----
url: https://angular.dev/api/forms/signals/FieldState
----

```
    
      interface FieldState<TValue, TKey extends string | number = string | number> extends ReadonlyFieldState<TValue, TKey> {
}
    
    
```

The [`FieldTree`](/api/forms/signals/FieldTree) associated with this field state.

A writable signal containing the value for this field.

Updating this signal will update the data model that the field is bound to.

While updates from the UI control are eventually reflected here, they may be delayed if debounced.

A signal containing the value of the control to which this field is bound.

This differs from [`value`](/api/forms/signals/FieldState#value) in that it's not subject to debouncing, and thus is used to buffer debounced updates from the control to the field. This will also not take into account the [`controlValue`](/api/forms/signals/FieldState#controlValue) of children.

Sets the dirty status of the field to `true`.

@returns`void`

Sets the touched status of the field and its descendants to `true`.

@paramoptions`MarkAsTouchedOptions | undefined`

Options for marking the field as touched.

@returns`void`

Gets the first validation error of the given kind on this field.

This method is reactive and will re-evaluate when the field's errors change if called within a reactive context (e.g. [`computed`](/api/core/computed) or [`effect`](/api/core/effect)).

@paramkind`K`

The kind of error (e.g. 'required', 'min').

@returns`((Extract<RequiredValidationError, { kind: K; }> | Extract<MinValidationError, { kind: K; }> | Extract<MinDateValidationError, { kind: K; }> | Extract<MaxValidationError, { kind: K; }> | Extract<MaxDateValidationError, { kind: K; }> | Extract<MinLengthValidationError, { kind: K; }> | Extract<MaxLengthValidationError, { kind: K; }> | Extract<PatternValidationError, { kind: K; }> | Extract<EmailValidationError, { kind: K; }> | Extract<StandardSchemaValidationError, { kind: K; }> | Extract<NativeInputParseError, { kind: K; }>) & WithFieldTree) | undefined`

The first matching error, or `undefined` if none.

@paramkind`string`

@returns`WithFieldTree | undefined`

Resets the [`touched`](/api/forms/signals/FieldState#touched) and [`dirty`](/api/forms/signals/FieldState#dirty) state of the field and its descendants.

Note this does not change the data model, which can be reset directly if desired.

@paramvalue`TValue | undefined`

Optional value to set to the form. If not passed, the value will not be changed.

@returns`void`

Reloads all asynchronous validators for this field and its descendants.

@returns`void`

```
@if (!field.hidden()) {
  ...
}
```

A signal containing the [`errors`](/api/forms/signals/FieldState#errors) of the field itself, excluding its descendants.

A signal containing the [`errors`](/api/forms/signals/FieldState#errors) of the field and its descendants.

----
url: https://angular.dev/api/forms/signals/email
----

```
function email<TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<string, 1, TPathKind>,
  config?: BaseValidatorConfig<string, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<string, 1, TPathKind>`

Path of the field to validate

@paramconfig`BaseValidatorConfig<string, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`emailError()`](/api/forms/signals/emailError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/provideNoopAnimations
----

```
function provideNoopAnimations(): Provider[];
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@returns`Provider[]`

## [Usage Notes](#usage-notes)

The function is useful when you want to bootstrap an application using the `bootstrapApplication` function, but you need to disable animations (for example, when running tests).

```
bootstrapApplication(RootComponent, {
  providers: [
    provideNoopAnimations()
  ]
});
```

Jump to details

----
url: https://angular.dev/api/core/Version
----

```
    
      class Version {}
    
    
```

@paramfull`string`

@returns`Version`

Jump to details

----
url: https://angular.dev/api/platform-browser/createApplication
----

```
function createApplication(
  options?: ApplicationConfig | undefined,
  context?: BootstrapContext | undefined,
): Promise<ApplicationRef>;
```

@paramoptions`ApplicationConfig | undefined`

Extra configuration for the application environment, see [`ApplicationConfig`](/api/core/ApplicationConfig) for additional info.

@paramcontext`BootstrapContext | undefined`

Optional context object that can be used to provide a pre-existing platform injector. This is useful for advanced use-cases, for example, server-side rendering, where the platform is created for each request.

@returns`Promise<ApplicationRef>`

A promise that returns an [`ApplicationRef`](/api/core/ApplicationRef) instance once resolved.

Jump to details

----
url: https://angular.dev/api/platform-browser/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/selenium-webdriver/waitForAngularReady
----

```
function waitForAngularReady(wd: webdriver.WebDriver): Promise<void>;
```

@paramwd`webdriver.WebDriver`

@returns`Promise<void>`

Jump to details

----
url: https://angular.dev/api/common/http/testing/HttpTestingController
----

```
    
      abstract class HttpTestingController {
}
    
    
```

Search for requests that match the given parameter, without any expectations.

@parammatch`string | RequestMatch | ((req: HttpRequest<any>) => boolean)`

@returns`TestRequest[]`

Expect that a single request has been made which matches the given URL, and return its mock.

If no such request has been made, or more than one such request has been made, fail with an error message including the given request description, if any.

@paramurl`string`

@paramdescription`string | undefined`

@returns`TestRequest`

Expect that a single request has been made which matches the given parameters, and return its mock.

If no such request has been made, or more than one such request has been made, fail with an error message including the given request description, if any.

@paramparams`RequestMatch`

@paramdescription`string | undefined`

@returns`TestRequest`

Expect that a single request has been made which matches the given predicate function, and return its mock.

If no such request has been made, or more than one such request has been made, fail with an error message including the given request description, if any.

@parammatchFn`(req: HttpRequest<any>) => boolean`

@paramdescription`string | undefined`

@returns`TestRequest`

Expect that a single request has been made which matches the given condition, and return its mock.

If no such request has been made, or more than one such request has been made, fail with an error message including the given request description, if any.

@parammatch`string | RequestMatch | ((req: HttpRequest<any>) => boolean)`

@paramdescription`string | undefined`

@returns`TestRequest`

Expect that no requests have been made which match the given URL.

If a matching request has been made, fail with an error message including the given request description, if any.

@paramurl`string`

@paramdescription`string | undefined`

@returns`void`

Expect that no requests have been made which match the given parameters.

If a matching request has been made, fail with an error message including the given request description, if any.

@paramparams`RequestMatch`

@paramdescription`string | undefined`

@returns`void`

Expect that no requests have been made which match the given predicate function.

If a matching request has been made, fail with an error message including the given request description, if any.

@parammatchFn`(req: HttpRequest<any>) => boolean`

@paramdescription`string | undefined`

@returns`void`

Expect that no requests have been made which match the given condition.

If a matching request has been made, fail with an error message including the given request description, if any.

@parammatch`string | RequestMatch | ((req: HttpRequest<any>) => boolean)`

@paramdescription`string | undefined`

@returns`void`

Verify that no unmatched requests are outstanding.

If any requests are outstanding, fail with an error message indicating which requests were not handled.

If `ignoreCancelled` is not set (the default), `verify()` will also fail if cancelled requests were not explicitly matched.

@paramopts`{ ignoreCancelled?: boolean | undefined; } | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/forms/signals/MAX
----

```
    
      const MAX: LimitSelectionKey;
    
    
```

## [Description](#description)

A [`MetadataKey`](/api/forms/signals/MetadataKey) that points to another key determining the maximum value of the field.

This indirection allows different keys to be used for different types of values with their own reducers, such as [`MAX_DATE`](/api/forms/signals/MAX_DATE) and [`MAX_NUMBER`](/api/forms/signals/MAX_NUMBER).

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/ANIMATION_MODULE_TYPE
----

## [API](#api)

```
    
      const ANIMATION_MODULE_TYPE: InjectionToken<"NoopAnimations" | "BrowserAnimations">;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/UrlTree
----

```
    
      class UrlTree {}
    
    
```

@paramroot`UrlSegmentGroup`

The root segment group of the URL tree

@paramqueryParams`Params`

The query params of the URL

@paramfragment`string | null`

The fragment of the URL

@returns`UrlTree`

The root segment group of the URL tree

The query params of the URL

The fragment of the URL

## [Description](#description)

Represents the parsed URL.

Since a router state is a tree, and the URL is nothing but a serialized state, the URL is a serialized tree. UrlTree is a data structure that provides a lot of affordances in dealing with URLs

## [Usage Notes](#usage-notes)

### [Example](#example)

```
@Component({templateUrl:'template.html'})
class MyComponent {
  constructor(router: Router) {
    const tree: UrlTree =
      router.parseUrl('/team/33/(user/victor//support:help)?debug=true#fragment');
    const f = tree.fragment; // return 'fragment'
    const q = tree.queryParams; // returns {debug: 'true'}
    const g: UrlSegmentGroup = tree.root.children[PRIMARY_OUTLET];
    const s: UrlSegment[] = g.segments; // returns 2 segments 'team' and '33'
    g.children[PRIMARY_OUTLET].segments; // returns 2 segments 'user' and 'victor'
    g.children['support'].segments; // return 1 segment 'help'
  }
}
```

Jump to details

----
url: https://angular.dev/api/ssr/AngularAppEngineOptions
----

```
    
      interface AngularAppEngineOptions {}
    
    
```

A set of allowed hostnames for the server application.

Extends the scope of trusted proxy headers (`Forwarded` or `X-Forwarded-*`).

Jump to details

----
url: https://angular.dev/errors/NG3003
----

A component, directive, or pipe that is referenced by this component would require the compiler to add an import that would lead to a cycle of imports. For example, consider a scenario where a `Parent` references a `Child` in its template:

### parent.ts

```
import {Component} from '@angular/core';
import {Child} from './child';

@Component({
  selector: 'app-parent',
  imports: [Child],
  template: '<app-child/>',
})
export class Parent {}
```

### child.ts

```
import {Component, inject} from '@angular/core';
import {Parent} from './parent';

@Component({
  selector: 'app-child',
  template: 'The child!',
})
export class Child {
  private parent = inject(Parent);
}
```

There is already an import from `child.ts` to `parent.ts` since the `Child` references the `Parent` in its constructor.

**HELPFUL:** The parent component's template contains `<child></child>`. The generated code for this template must therefore contain a reference to the `Child` class. In order to make this reference, the compiler would have to add an import from `parent.ts` to `child.ts`, which would cause an import cycle:

```
parent.ts -> child.ts -> parent.ts
```

## [Remote Scoping](#remote-scoping)

If you are using NgModules, to avoid adding imports that create cycles, additional code is added to the [`NgModule`](/api/core/NgModule) class where the component that wires up the dependencies is declared.

This is known as "remote scoping".

## [Libraries](#libraries)

Unfortunately, "remote scoping" code is side-effectful —which prevents tree shaking— and cannot be used in libraries. So when building libraries using the `"compilationMode": "partial"` setting, any component that would require a cyclic import will cause this `NG3003` compiler error to be raised.

## [Debugging the error](#debugging-the-error)

The cycle that would be generated is shown as part of the error message. For example:

```
The component Child is used in the template but importing it would create a cycle:
/parent.ts -> /child.ts -> /parent.ts
```

Use this to identify how the referenced component, pipe, or directive has a dependency back to the component being compiled. Here are some ideas for fixing the problem:

* Try to rearrange your dependencies to avoid the cycle. For example, using an intermediate interface that is stored in an independent file that can be imported to both dependent files without causing an import cycle.
* Move the classes that reference each other into the same file, to avoid any imports between them.
* Convert import statements to type-only imports (using `import type` syntax) if the imported declarations are only used as types, as type-only imports do not contribute to cycles.

----
url: https://angular.dev/reference/migrations
----

Learn about how you can migrate your existing angular project to the latest features incrementally.

### [Standalone](reference/migrations/standalone)

[Standalone components provide a simplified way to build Angular applications. Standalone components specify their dependencies directly instead of getting them through NgModules.](reference/migrations/standalone)

[Migrate now](reference/migrations/standalone)

### [Control Flow Syntax](reference/migrations/control-flow)

[Built-in Control Flow Syntax allows you to use more ergonomic syntax which is close to JavaScript and has better type checking. It replaces the need to import `CommonModule` to use functionality like `*ngFor`, `*ngIf` and `*ngSwitch`.](reference/migrations/control-flow)

[Migrate now](reference/migrations/control-flow)

### [inject() Function](reference/migrations/inject-function)

[Angular's `inject` function offers more accurate types and better compatibility with standard decorators, compared to constructor-based injection.](reference/migrations/inject-function)

[Migrate now](reference/migrations/inject-function)

### [Lazy-loaded routes](reference/migrations/route-lazy-loading)

[Convert eagerly loaded component routes to lazy loaded ones. This allows the build process to split production bundles into smaller chunks, to load less JavaScript at initial page load.](reference/migrations/route-lazy-loading)

[Migrate now](reference/migrations/route-lazy-loading)

### [New `input()` API](reference/migrations/signal-inputs)

[Convert existing `@Input` fields to the new signal input API that is now production ready.](reference/migrations/signal-inputs)

[Migrate now](reference/migrations/signal-inputs)

### [New `output()` function](reference/migrations/outputs)

[Convert existing `@Output` custom events to the new output function that is now production ready.](reference/migrations/outputs)

[Migrate now](reference/migrations/outputs)

### [Queries as signal](reference/migrations/signal-queries)

[Convert existing decorator query fields to the improved signal queries API. The API is now production ready.](reference/migrations/signal-queries)

[Migrate now](reference/migrations/signal-queries)

### [Cleanup unused imports](reference/migrations/cleanup-unused-imports)

[Clean up unused imports in your project.](reference/migrations/cleanup-unused-imports)

[Try it now](reference/migrations/cleanup-unused-imports)

### [Self-closing tags](reference/migrations/self-closing-tags)

[Convert component templates to use self-closing tags where possible.](reference/migrations/self-closing-tags)

[Migrate now](reference/migrations/self-closing-tags)

### [NgClass to Class Bindings](reference/migrations/ngclass-to-class)

[Convert component templates to prefer class bindings over the `NgClass` directives when possible.](reference/migrations/ngclass-to-class)

[Migrate now](reference/migrations/ngclass-to-class)

### [NgStyle to Style Bindings](reference/migrations/ngstyle-to-style)

[Convert component templates to prefer style bindings over the `NgStyle` directives when possible.](reference/migrations/ngstyle-to-style)

[Migrate now](reference/migrations/ngstyle-to-style)

### [RouterTestingModule migration](reference/migrations/router-testing-module-migration)

[Convert `RouterTestingModule` usages to `RouterModule` in TestBed configurations and add `provideLocationMocks()` when appropriate.](reference/migrations/router-testing-module-migration)

[Migrate now](reference/migrations/router-testing-module-migration)

### [CommonModule to standalone imports](reference/migrations/common-to-standalone)

[Replace imports of the `CommonModule` with imports of the individual directives and pipes used in the templates when possible.](reference/migrations/common-to-standalone)

[Migrate now](reference/migrations/common-to-standalone)

### [Injectable to Service](reference/migrations/injectable-to-service)

[Convert eligible `@Injectable` classes to the `@Service` decorator.](reference/migrations/injectable-to-service)

[Migrate now](reference/migrations/injectable-to-service)

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragPreview
----

```
    
      class CdkDragPreview<T = any> implements OnDestroy {}
    
    
```

Context data to be added to the preview template instance.

Whether the preview should preserve the same size as the item that is being dragged.

@returns`void`

Jump to details

----
url: https://angular.dev/api/common/getLocaleWeekEndRange
----

Angular recommends relying on the `Intl` API for i18n. Intl's [`getWeekInfo`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo) has partial support (Chromium M99 & Safari 17). Libraries like [`Luxon`](https://moment.github.io/luxon/#/) rely on `Intl` but fallback on the ISO 8601 definition (Saturday+Sunday) if `getWeekInfo` is not supported .

## [API](#api)

```
function getLocaleWeekEndRange(locale: string): [WeekDay, WeekDay];
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Intl's [`getWeekInfo`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo) has partial support (Chromium M99 & Safari 17). Libraries like [`Luxon`](https://moment.github.io/luxon/#/) rely on `Intl` but fallback on the ISO 8601 definition (Saturday+Sunday) if `getWeekInfo` is not supported .

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`[WeekDay, WeekDay]`

The range of day values, `[startDay, endDay]`.

Jump to details

----
url: https://angular.dev/api/cdk/testing/AsyncPredicate
----

```
    
      type AsyncPredicate<T> = (item: T) => Promise<boolean>
    
    
```

Jump to details

----
url: https://angular.dev/guide/forms/signals/custom-controls
----

**NOTE:** This guide assumes familiarity with [Signal Forms essentials](essentials/signal-forms).

The browser's built-in form controls (like input, select, textarea) handle common cases, but applications often need specialized inputs. A date picker with calendar UI, a rich text editor with formatting toolbar, or a tag selector with autocomplete all require custom implementations.

Signal Forms works with any component that implements specific interfaces. A **control interface** defines the properties and signals that allow your component to communicate with the form system. When your component implements one of these interfaces, the `[formField]` directive automatically connects your control to form state, validation, and data binding.

**HELPFUL:** Custom Signal Form Controls [can be used](guide/forms/signals/migration#custom-controls) with Signal, Reactive and Template-Driven Forms without any extra compatibility code.

## [Creating a basic custom control](#creating-a-basic-custom-control)

Let's start with a minimal implementation and add features as needed.

### [Minimal input control](#minimal-input-control)

A basic custom input only needs to implement the [`FormValueControl`](/api/forms/signals/FormValueControl) interface and define the required `value` model signal.

```
import {Component, model} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';

@Component({
  selector: 'app-basic-input',
  template: `
    <div class="basic-input">
      <input
        type="text"
        [value]="value()"
        (input)="value.set($event.target.value)"
        placeholder="Enter text..."
      />
    </div>
  `,
})
export class BasicInput implements FormValueControl<string> {
  /** The current input value */
  value = model('');
}
```

### [Minimal checkbox control](#minimal-checkbox-control)

A checkbox-style control needs two things:

1. Implement the [`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl) interface so the [`FormField`](/api/forms/signals/FormField) directive will recognize it as a form control
2. Provide a `checked` model signal

```
import {Component, model, ChangeDetectionStrategy} from '@angular/core';
import {FormCheckboxControl} from '@angular/forms/signals';

@Component({
  selector: 'app-basic-toggle',
  template: `
    <button type="button" [class.active]="checked()" (click)="toggle()">
      <span class="toggle-slider"></span>
    </button>
  `,
})
export class BasicToggle implements FormCheckboxControl {
  /** Whether the toggle is checked */
  checked = model<boolean>(false);

  toggle() {
    this.checked.update((val) => !val);
  }
}
```

### [Using your custom control](#using-your-custom-control)

Once you've created a control, you can use it anywhere you would use a built-in input by adding the [`FormField`](/api/forms/signals/FormField) directive to it:

```
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';
import {BasicInput} from './basic-input';
import {BasicToggle} from './basic-toggle';

@Component({
  imports: [FormField, BasicInput, BasicToggle],
  template: `
    <form novalidate>
      <label>
        Email
        <app-basic-input [formField]="registrationForm.email" />
      </label>

      <label>
        Accept terms
        <app-basic-toggle [formField]="registrationForm.acceptTerms" />
      </label>

      <button type="submit" [disabled]="registrationForm().invalid()">Register</button>
    </form>
  `,
})
export class Registration {
  registrationModel = signal({
    email: '',
    acceptTerms: false,
  });

  registrationForm = form(this.registrationModel, (schemaPath) => {
    required(schemaPath.email, {message: 'Email is required'});
    required(schemaPath.acceptTerms, {message: 'You must accept the terms'});
  });
}
```

**NOTE:** The schema callback parameter (`schemaPath` in these examples) is a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) object that provides paths to all fields in your form. You can name this parameter anything you like.

The `[formField]` directive works identically for custom controls and built-in inputs. Signal Forms treats them the same - validation runs, state updates, and data binding works automatically.

## [Understanding control interfaces](#understanding-control-interfaces)

Now that you've seen custom controls in action, let's explore how they integrate with Signal Forms.

### [Control interfaces](#control-interfaces)

The `BasicInput` and `BasicToggle` components you created implement specific control interfaces that tell Signal Forms how to interact with them.

#### [FormValueControl](#formvaluecontrol)

[`FormValueControl`](/api/forms/signals/FormValueControl) is the interface for most input types - text inputs, number inputs, date pickers, select dropdowns, and any control that edits a single value. When your component implements this interface:

* **Required property**: Your component must provide a `value` model signal
* **What the FormField directive does**: Binds the form field's value to your control's `value` signal

**IMPORTANT:** Controls implementing [`FormValueControl`](/api/forms/signals/FormValueControl) must NOT have a `checked` property

#### [FormCheckboxControl](#formcheckboxcontrol)

[`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl) is the interface for checkbox-like controls - toggles, switches, and any control that represents a boolean on/off state. When your component implements this interface:

* **Required property**: Your component must provide a `checked` model signal
* **What the FormField directive does**: Binds the form field's value to your control's `checked` signal

**IMPORTANT:** Controls implementing [`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl) must NOT have a `value` property

### [Optional state properties](#optional-state-properties)

Both [`FormValueControl`](/api/forms/signals/FormValueControl) and [`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl) extend [`FormUiControl`](/api/forms/signals/FormUiControl) - a base interface that provides optional properties for integrating with form state.

All properties are optional. Implement only what your control needs.

#### [Interaction state](#interaction-state)

Track when users interact with your control:

| Property  | Purpose                                          |
| --------- | ------------------------------------------------ |
| `touched` | Whether the user has interacted with the field   |
| `dirty`   | Whether the value differs from its initial state |

#### [Validation state](#validation-state)

Display validation feedback to users:

| Property  | Purpose                                 |
| --------- | --------------------------------------- |
| `errors`  | Array of current validation errors      |
| `invalid` | Whether the field has validation errors |
| `pending` | Whether async validation is in progress |

#### [Availability state](#availability-state)

Control whether users can interact with your field:

| Property          | Purpose                                                  |
| ----------------- | -------------------------------------------------------- |
| `disabled`        | Whether the field is disabled                            |
| `disabledReasons` | Reasons why the field is disabled                        |
| `readonly`        | Whether the field is readonly (visible but not editable) |
| `hidden`          | Whether the field is hidden from view                    |

**NOTE:** `disabledReasons` is an array of [`DisabledReason`](/api/forms/signals/DisabledReason) objects. Each object has a `fieldTree` property (reference to the field tree) and an optional `message` property. Access the message via `reason.message`.

#### [Validation constraints](#validation-constraints)

Receive validation constraint values from the form:

| Property    | Purpose                                              |
| ----------- | ---------------------------------------------------- |
| `required`  | Whether the field is required                        |
| `min`       | Minimum numeric value (`undefined` if no constraint) |
| `max`       | Maximum numeric value (`undefined` if no constraint) |
| `minLength` | Minimum string length (undefined if no constraint)   |
| `maxLength` | Maximum string length (undefined if no constraint)   |
| `pattern`   | Array of regular expression patterns to match        |

#### [Field metadata](#field-metadata)

| Property | Purpose                                                            |
| -------- | ------------------------------------------------------------------ |
| `name`   | The field's name attribute (which is unique across forms and apps) |

The "[Adding state signals](#adding-state-signals)" section below shows how to implement these properties in your controls.

### [How the FormField directive works](#how-the-formfield-directive-works)

The `[formField]` directive detects which interface your control implements and automatically binds the appropriate signals:

```
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';
import {CustomInput} from './custom-input';
import {CustomToggle} from './custom-toggle';

@Component({
  selector: 'app-my-form',
  imports: [FormField, CustomInput, CustomToggle],
  template: `
    <form novalidate>
      <app-custom-input [formField]="userForm.username" />
      <app-custom-toggle [formField]="userForm.subscribe" />
    </form>
  `,
})
export class MyForm {
  formModel = signal({
    username: '',
    subscribe: false,
  });

  userForm = form(this.formModel, (schemaPath) => {
    required(schemaPath.username, {message: 'Username is required'});
  });
}
```

**TIP:** For complete coverage of creating and managing form models, see the [Form Models guide](guide/forms/signals/models).

When you bind `[formField]="userForm.username"`, the FormField directive:

1. Detects your control implements [`FormValueControl`](/api/forms/signals/FormValueControl)
2. Internally accesses `userForm.username().value()` and binds it to your control's `value` model signal
3. Binds form state signals ([`disabled()`](/api/forms/signals/disabled), `errors()`, etc.) to your control's optional input signals
4. Updates occur automatically through signal reactivity

## [Adding state signals](#adding-state-signals)

The minimal controls shown above work, but they don't respond to form state. You can add optional input signals to make your controls react to disabled state, display validation errors, and track user interaction.

Here's a comprehensive example that implements common state properties:

```
import {Component, model, input, output, ChangeDetectionStrategy} from '@angular/core';
import {
  FormValueControl,
  WithOptionalFieldTree,
  ValidationError,
  DisabledReason,
} from '@angular/forms/signals';

@Component({
  selector: 'app-stateful-input',
  template: `
    @if (!hidden()) {
      <div class="input-container">
        <input
          type="text"
          [value]="value()"
          (input)="value.set($event.target.value)"
          [disabled]="disabled()"
          [readonly]="readonly()"
          [class.invalid]="invalid()"
          [attr.aria-invalid]="invalid()"
          (blur)="touch.emit()"
        />

        @if (invalid()) {
          <div class="error-messages" role="alert">
            @for (error of errors(); track error) {
              <span class="error">{{ error.message }}</span>
            }
          </div>
        }

        @if (disabled() && disabledReasons().length > 0) {
          <div class="disabled-reasons">
            @for (reason of disabledReasons(); track reason) {
              <span>{{ reason.message }}</span>
            }
          </div>
        }
      </div>
    }
  `,
})
export class StatefulInput implements FormValueControl<string> {
  // Required
  value = model<string>('');

  // Writable interaction state - control updates these
  touched = input<boolean>(false);
  touch = output<void>();

  // Read-only state - form system manages these
  disabled = input<boolean>(false);
  disabledReasons = input<readonly WithOptionalFieldTree<DisabledReason>[]>([]);
  readonly = input<boolean>(false);
  hidden = input<boolean>(false);
  invalid = input<boolean>(false);
  errors = input<readonly WithOptionalFieldTree<ValidationError>[]>([]);
}
```

As a result, you can use the control with validation and state management:

```
import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
import {form, FormField, required, email} from '@angular/forms/signals';
import {StatefulInput} from './stateful-input';

@Component({
  imports: [FormField, StatefulInput],
  template: `
    <form novalidate>
      <label>
        Email
        <app-stateful-input [formField]="loginForm.email" />
      </label>
    </form>
  `,
})
export class Login {
  loginModel = signal({email: ''});

  loginForm = form(this.loginModel, (schemaPath) => {
    required(schemaPath.email, {message: 'Email is required'});
    email(schemaPath.email, {message: 'Enter a valid email address'});
  });
}
```

When the user types an invalid email, the FormField directive automatically updates `invalid()` and `errors()`. Your control can display the validation feedback.

### [Working with `debounce('blur')`](#working-with-debounceblur)

The [`debounce('blur')`](api/forms/signals/debounce) rule delays updates from the UI to the form model until the field is blurred, instead of applying them on every keystroke. Built-in controls report a blur to the form automatically. A custom control only participates if it emits its `touch` output in response to the native [`blur` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event):

```
import {Component, model, output} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';

@Component({
  selector: 'app-custom-input',
  template: `
    <input
      type="text"
      [value]="value()"
      (input)="value.set($event.target.value)"
      (blur)="touch.emit()"
    />
  `,
})
export class CustomInput implements FormValueControl<string> {
  value = model('');
  touch = output<void>();
}
```

With the `touch` output in place, `debounce('blur')` behaves the same for your control as it does for built-in inputs:

```
import {Component, signal} from '@angular/core';
import {debounce, form, FormField} from '@angular/forms/signals';
import {CustomInput} from './custom-input';

@Component({
  selector: 'app-root',
  imports: [CustomInput, FormField],
  template: `<app-custom-input [formField]="userForm.name" />`,
})
export class App {
  userModel = signal({name: ''});

  userForm = form(this.userModel, (schemaPath) => {
    debounce(schemaPath.name, 'blur');
  });
}
```

**IMPORTANT:** Emit `touch` on `blur` (when focus leaves the control), not on `focus`. Without the `touch` output the field never registers as blurred, so `debounce('blur')` has no effect on your control.

## [Value transformation](#value-transformation)

Controls sometimes display values differently than the form model stores them - a date picker might display "January 15, 2024" while storing "2024-01-15", or a currency input might show "$1,234.56" while storing 1234.56.

Use [`transformedValue()`](/api/forms/signals/transformedValue) (from `@angular/forms/signals`) to keep the raw value shown in the UI in sync with the model value. It takes the control's `value` model signal plus a `parse` and a `format` function, and returns a writable signal holding the raw value:

* `format` converts the model value into the raw value the template renders.
* `parse` converts what the user typed back into a model value, and can report parse errors instead.

```
import {Component, model} from '@angular/core';
import {FormValueControl, transformedValue} from '@angular/forms/signals';

@Component({
  selector: 'number-input',
  template: `
    <input type="text" [value]="rawValue()" (input)="rawValue.set($event.target.value)" />
  `,
})
export class NumberInput implements FormValueControl<number | null> {
  readonly value = model.required<number | null>();

  protected readonly rawValue = transformedValue(this.value, {
    parse: (val: string): {value: number | null} => ({value: val ? Number(val) : null}),
    format: (val: number | null): string => val?.toString() ?? '',
  });
}
```

Writing to the returned signal (`rawValue.set(...)`) runs `parse` and writes the result into `value`. Whenever the model changes from elsewhere - a `reset()`, a schema rule, or another part of the app - `format` runs again and the raw value updates to match.

### [Reporting parse errors](#reporting-parse-errors)

Sometimes the raw value has no valid model representation - a half-typed date, or letters in a numeric field. The `NumberInput` above has this problem: `Number('abc')` is `NaN`, which `parse` happily writes into the model.

Return `{error}` instead:

```
export class NumberInput implements FormValueControl<number | null> {
  readonly value = model.required<number | null>();

  protected readonly rawValue = transformedValue(this.value, {
    parse: (val) => {
      const parsed = val ? Number(val) : null;

      return Number.isNaN(parsed)
        ? {error: {kind: 'parse', message: `${val} is not a number`}}
        : {value: parsed};
    },
    format: (val) => val?.toString() ?? '',
  });
}
```

Return both `value` and `error` when you want to update the model *and* flag a problem.

When the control is bound with `[formField]`, parse errors are automatically reported to the field, so they show up in the field's `errors()` signal alongside validation errors:

```
@Component({
  imports: [NumberInput, FormField],
  template: `
    <number-input [formField]="orderForm.amount" />

    @for (error of orderForm.amount().errors(); track $index) {
      <!-- {kind: 'parse', message: '...'} is reported here too -->
      <p class="error">{{ error.message }}</p>
    }
  `,
})
export class Order {
  orderModel = signal<{amount: number | null}>({amount: null});
  orderForm = form(this.orderModel);
}
```

A field with parse errors is invalid, which blocks submission the same way a failed validation rule does.

**HELPFUL:** Signal Forms uses the same mechanism for native inputs. When the browser cannot parse a value (for example, a partially typed date in `<input type="date">`), it surfaces as a `parse` error on the field. See [Native HTML validation](guide/forms/signals/validation#native-html-validation) for details.

### [Resetting](#resetting)

Calling `reset()` on the field clears any pending parse errors and re-formats the raw value from the model, so a control left in an unparseable state returns to a clean display value:

```
orderForm.amount().reset();
```

## [Validation integration](#validation-integration)

Controls display validation state but don't perform validation. Validation happens in the form schema - your control receives `invalid()` and `errors()` signals from the FormField directive and displays them (as shown in the StatefulInput example above).

The FormField directive also passes validation constraint values like `required`, `min`, `max`, `minLength`, `maxLength`, and `pattern`. Your control can use these to enhance the UI:

```
export class NumberInput implements FormValueControl<number> {
  value = model<number>(0);

  // Constraint values from schema validation rules
  required = input<boolean>(false);
  min = input<number | undefined>(undefined);
  max = input<number | undefined>(undefined);
}
```

When you add `min()` and `max()` validation rules to the schema, the FormField directive passes these values to your control. Use them to apply HTML5 attributes or show constraint hints in your template.

**IMPORTANT:** Don't implement validation logic in your control. Define validation rules in the form schema and let your control display the results:

Avoid

```
// Avoid: Validation in control
export class BadControl implements FormValueControl<string> {
  value = model<string>('');
  isValid() {
    return this.value().length >= 8;
  } // Don't do this!
}
```

Prefer

```
// Good: Validation in schema, control displays results
accountForm = form(this.accountModel, (schemaPath) => {
  minLength(schemaPath.password, 8, {message: 'Password must be at least 8 characters'});
});
```

## [Making controls reusable](#making-controls-reusable)

A custom control often carries implicit expectations about validation. An email input needs `required` and `email` rules in every form that uses it. Instead of relying on each consumer to redeclare those rules, package a companion schema alongside the control and export both from the same module:

### email-input.ts

```
import {schema, required, email} from '@angular/forms/signals';

export const emailFieldSchema = schema<string>((path) => {
  required(path, {message: 'Email is required'});
  email(path, {message: 'Enter a valid email address'});
});
```

A consumer imports the companion schema and composes it into their form with [`apply()`](/api/forms/signals/apply):

### registration.ts

```
import {form, apply} from '@angular/forms/signals';
import {emailFieldSchema} from './email-input';

registrationForm = form(this.registrationModel, (path) => {
  apply(path.email, emailFieldSchema);
});
```

[`apply()`](/api/forms/signals/apply) merges the companion schema's rules into the parent form at the specified path. The consumer can still add more rules to the same field because [`apply()`](/api/forms/signals/apply) composes with other rules rather than replacing them. See the [Schemas guide](guide/forms/signals/schemas) for complete coverage of [`schema()`](/api/forms/signals/schema), [`apply()`](/api/forms/signals/apply), and conditional composition with [`applyWhen()`](/api/forms/signals/applyWhen).

### [Design considerations](#design-considerations)

The consumer's model must initialize every field with a defined value. In Signal Forms, `undefined` signifies the absence of a field and not an empty value. For a reusable email control, that means the consumer should use `''` as the initial value, and not leave the property undefined. See the [Form Models guide](guide/forms/signals/models) for details on choosing initial values.

In addition, controls should not register their own effects for state management. The form system manages field state through internal effects. This means that your control receives state updates through input signals. If a control needs to transform values, use [`transformedValue()`](/api/forms/signals/transformedValue) as shown in the "[Value transformation](#value-transformation)" section rather than an [`effect()`](/api/core/effect).

## [Next steps](#next-steps)

This guide covered building custom controls that integrate with Signal Forms. Related guides explore other aspects of Signal Forms:

----
url: https://angular.dev/api/common/http/HttpResourceRequest
----

```
    
      interface HttpResourceRequest {}
    
    
```

URL of the request.

This URL should not include query parameters. Instead, specify query parameters through the `params` field.

HTTP method of the request, which defaults to GET if not specified.

Body to send with the request, if there is one.

If no Content-Type header is specified by the user, Angular will attempt to set one based on the type of `body`.

Dictionary of query parameters which will be appeneded to the request URL.

Dictionary of headers to include with the outgoing request.

Context of the request stored in a dictionary of key-value pairs.

If `true`, progress events will be enabled for the request and delivered through the `HttpResource.progress` signal.

Specifies whether the `withCredentials` flag should be set on the outgoing request.

This flag causes the browser to send cookies and other authentication information along with the request.

When using the fetch implementation and set to `true`, the browser will not abort the associated request if the page that initiated it is unloaded before the request is complete.

Controls how the request will interact with the browser's HTTP cache. This affects whether a response is retrieved from the cache, how it is stored, or if it bypasses the cache altogether.

The credentials mode of the request, which determines how cookies and other authentication information are handled. This can affect whether credentials are sent with cross-origin requests or not.

Indicates the relative priority of the request. This may be used by the browser to decide the order in which requests are dispatched and resources fetched.

The mode of the request, which determines how the request will interact with the browser's security model. This can affect things like CORS (Cross-Origin Resource Sharing) and same-origin policies.

The redirect mode of the request, which determines how redirects are handled. This can affect whether the request follows redirects automatically, or if it fails when a redirect occurs.

The referrer of the request, which can be used to indicate the origin of the request. This is useful for security and analytics purposes. Value is a same-origin URL, "about:client", or the empty string, to set request's referrer.

The referrer policy of the request, which can be used to specify the referrer information to be included with the request. This can affect the amount of referrer information sent with the request, and can be used to enhance privacy and security.

The integrity metadata of the request, which can be used to ensure the request is made with the expected content. A cryptographic hash of the resource to be fetched by request

Configures the server-side rendering transfer cache for this request.

See the documentation on the transfer cache for more information.

The timeout for the backend HTTP request in ms.

Jump to details

----
url: https://angular.dev/api/elements/NgElementConfig
----

```
    
      interface NgElementConfig {}
    
    
```

The injector to use for retrieving the component's factory.

An optional custom strategy factory to use instead of the default. The strategy controls how the transformation is performed.

Jump to details

----
url: https://angular.dev/guide/components/dom-apis
----

Angular handles most DOM creation, updates, and removals for you. However, you might rarely need to directly interact with a component's DOM. Components can inject ElementRef to get a reference to the component's host element:

```
@Component(/* ... */)
export class ProfilePhoto {
  constructor() {
    const elementRef = inject(ElementRef);
    console.log(elementRef.nativeElement);
  }
}
```

The `nativeElement` property references the host [Element](https://developer.mozilla.org/docs/Web/API/Element) instance.

You can use Angular's [`afterEveryRender`](/api/core/afterEveryRender) and [`afterNextRender`](/api/core/afterNextRender) functions to register a **render callback** that runs when Angular has finished rendering the page.

```
@Component(/* ... */)
export class ProfilePhoto {
  constructor() {
    const elementRef = inject(ElementRef);
    afterEveryRender(() => {
      // Focus the first input element in this component.
      elementRef.nativeElement.querySelector('input')?.focus();
    });
  }
}
```

[`afterEveryRender`](/api/core/afterEveryRender) and [`afterNextRender`](/api/core/afterNextRender) must be called in an *injection context*, typically a component's constructor.

**Avoid direct DOM manipulation whenever possible.** Always prefer expressing your DOM's structure in component templates and updating that DOM with bindings.

**Render callbacks never run during server-side rendering or build-time pre-rendering.**

**Never directly manipulate the DOM inside of other Angular lifecycle hooks**. Angular does not guarantee that a component's DOM is fully rendered at any point other than in render callbacks. Further, reading or modifying the DOM during other lifecycle hooks can negatively impact page performance by causing [layout thrashing](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing).

## [Using a component's renderer](#using-a-components-renderer)

Components can inject an instance of [`Renderer2`](/api/core/Renderer2) to perform certain DOM manipulations that are tied to other Angular features.

Any DOM elements created by a component's [`Renderer2`](/api/core/Renderer2) participate in that component's [style encapsulation](guide/components/styling#style-scoping).

Certain [`Renderer2`](/api/core/Renderer2) APIs also tie into Angular's animation system. You can use the `setProperty` method to update synthetic animation properties and the `listen` method to add event listeners for synthetic animation events. See the [Animations](guide/animations) guide for details.

Aside from these two narrow use-cases, there is no difference between using [`Renderer2`](/api/core/Renderer2) and native DOM APIs. [`Renderer2`](/api/core/Renderer2) APIs do not support DOM manipulation in server-side rendering or build-time pre-rendering contexts.

## [When to use DOM APIs](#when-to-use-dom-apis)

While Angular handles most rendering concerns, some behaviors may still require using DOM APIs. Some common use cases include:

* Managing element focus
* Measuring element geometry, such as with `getBoundingClientRect`
* Reading an element's text content
* Setting up native observers such as [`MutationObserver`](https://developer.mozilla.org/docs/Web/API/MutationObserver), [`ResizeObserver`](https://developer.mozilla.org/docs/Web/API/ResizeObserver), or [`IntersectionObserver`](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API).

Avoid inserting, removing, and modifying DOM elements. In particular, **never directly set an element's `innerHTML` property**, which can make your application vulnerable to [cross-site scripting (XSS) exploits](https://developer.mozilla.org/docs/Glossary/Cross-site_scripting). Angular's template bindings, including bindings for `innerHTML`, include safeguards that help protect against XSS attacks. See the [Security guide](best-practices/security) for details.

----
url: https://angular.dev/guide/signals/debounced
----

**IMPORTANT:** [`debounced`](/api/core/debounced) is [experimental](reference/releases#experimental). It's ready for you to try, but it might change before it is stable.

Use [`debounced`](/api/core/debounced) to delay reacting to a signal's value until it stops changing. It returns a [`Resource`](/api/core/Resource) whose value reflects the debounced value of the source signal.

```
import {Component, debounced, resource, signal} from '@angular/core';

@Component({
  template: `
    <input (input)="query.set($event.target.value)" />

    @if (results.isLoading()) {
      <p>Searching…</p>
    }
    @for (item of results.value(); track item.id) {
      <li>{{ item.name }}</li>
    }
  `,
})
export class Search {
  query = signal('');

  debouncedQuery = debounced(this.query, 300);

  results = resource({
    params: () => this.debouncedQuery.value(),
    loader: ({params}) => fetchResults(params),
  });
}
```

[`debounced`](/api/core/debounced) takes the source signal and a wait duration in milliseconds. The returned resource's `value()` always contains the last settled value, and `status()` tells you whether a new value is still pending.

## [Status during debounce](/guide/signals/debounced#status-during-debounce)

While the debounce timer is counting down, `status()` is `'loading'` and `value()` returns the previously resolved value. When the timer expires, the resource settles to `'resolved'`. If the source signal throws, the resource enters `'error'` immediately; no timer runs.

See [Resource status](/guide/signals/resource#resource-status) for the full list of statuses and their `value()` behavior.

## [Custom wait function](/guide/signals/debounced#custom-wait-function)

Instead of a millisecond duration, you can pass a function that returns a `Promise<void>`. The resource resolves when the promise resolves. If the source signal changes before the promise settles, Angular discards the previous promise and starts a new one.

```
debouncedQuery = debounced(query, (value, lastSnapshot) => {
  // Retry immediately after an error rather than making the user wait again.
  if (lastSnapshot.status === 'error') return;
  // Short queries get a longer delay—the user is likely still typing.
  const ms = value.length < 3 ? 500 : 200;
  return new Promise<void>((resolve) => setTimeout(resolve, ms));
});
```

See the [`DebounceTimer`](/api/core/DebounceTimer) type in the API reference for details.

## [Equality](/guide/signals/debounced#equality)

By default, [`debounced`](/api/core/debounced) uses `Object.is` to compare values.

Provide a custom equality function with the `equal` option when the default identity check is too strict:

```
debouncedFilter = debounced(filter, 200, {
  equal: (a, b) => a.category === b.category && a.minPrice === b.minPrice,
});
```

## [Injection context](/guide/signals/debounced#injection-context)

[`debounced`](/api/core/debounced) must be called inside an [injection context](guide/di/dependency-injection-context). Angular automatically destroys the debounced resource and cancels any pending timer when the injector is destroyed.

To use [`debounced`](/api/core/debounced) outside of an injection context, pass an explicit [`Injector`](/api/core/Injector) via the options:

```
@Service()
export class SearchService {
  private injector = inject(Injector);

  createDebouncedQuery(query: Signal<string>): Resource<string> {
    return debounced(query, 300, {injector: this.injector});
  }
}
```

----
url: https://angular.dev/api/common/http/FetchBackend
----

```
    
      class FetchBackend implements HttpBackend {}
    
    
```

@paramrequest`HttpRequest<any>`

@returns`Observable<HttpEvent<any>>`

## [Description](#description)

Uses `fetch` to send requests to a backend server.

This [`FetchBackend`](/api/common/http/FetchBackend) requires the support of the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) which is available on all supported browsers and on Node.js v18 or later.

Jump to details

----
url: https://angular.dev/api/router/RouteConfigLoadStart
----

```
    
      class RouteConfigLoadStart {}
    
    
```

@paramroute`Route`

@returns`RouteConfigLoadStart`

Jump to details

----
url: https://angular.dev/api/ssr/ServerRouteCommon
----

```
    
      interface ServerRouteCommon {}
    
    
```

The path associated with this route.

Optional additional headers to include in the response for this route.

Optional status code to return for this route.

Jump to details

----
url: https://angular.dev/cli/add
----

```
ng add <collection> [options]
```

Adds the npm package for a published library to your workspace, and configures the project in the current working directory to use that library, as specified by the library's schematic. For example, adding `@angular/pwa` configures your project for PWA support:

```
ng add @angular/pwa
```

## [Arguments](#arguments)

`collection`

The package to be added.

`registry`

The NPM registry to use.

Value Type`string`

`skip-confirmation`

Skip asking a confirmation prompt before installing and executing the package. Ensure package name is correct prior to using this option.

Value Type`boolean`Default`false`

`verbose`

Display additional details about internal operations during execution.

Value Type`boolean`Default`false`

Jump to details

----
url: https://angular.dev/api/core/OutputRef
----

```
    
      interface OutputRef<T> {}
    
    
```

Registers a callback that is invoked whenever the output emits a new value of type `T`.

Angular will automatically clean up the subscription when the directive/component of the output is destroyed.

@paramcallback`(value: T) => void`

@returns`OutputRefSubscription`

Jump to details

----
url: https://angular.dev/api/common/http/withXsrfConfiguration
----

```
function withXsrfConfiguration({
  cookieName,
  headerName,
}: {
  cookieName?: string | undefined;
  headerName?: string | undefined;
}): HttpFeature<HttpFeatureKind.CustomXsrfConfiguration>;
```

@param{ cookieName, headerName, }`{ cookieName?: string | undefined; headerName?: string | undefined; }`

@returns`HttpFeature<HttpFeatureKind.CustomXsrfConfiguration>`

## [Description](#description)

Customizes the XSRF protection for the configuration of the current [`HttpClient`](/api/common/http/HttpClient) instance.

This feature is incompatible with the [`withNoXsrfProtection`](/api/common/http/withNoXsrfProtection) feature.

Jump to details

----
url: https://angular.dev/api/core/StaticProvider
----

```
    
      type StaticProvider = | ValueProvider  | ExistingProvider  | StaticClassProvider  | ConstructorProvider  | FactoryProvider  | any[]
    
    
```

Jump to details

----
url: https://angular.dev/api/router/NavigationCancellationCode
----

```
    
      enum NavigationCancellationCode {}
    
    
```

A navigation failed because a guard returned a [`UrlTree`](/api/router/UrlTree) to redirect.

A navigation failed because a more recent navigation started.

A navigation failed because one of the resolvers completed without emitting a value.

A navigation failed because a guard returned `false`.

A navigation was aborted by the [`Navigation.abort`](/api/router/Navigation#abort) function.

Jump to details

----
url: https://angular.dev/api/common/JsonPipe
----

```
{{ value_expression | json }}
```

## [API](#api)

```
    
      class JsonPipe implements PipeTransform {}
    
    
```

@paramvalue`unknown`

A value of any type to convert into a JSON-format string.

@returns`string`

## [Description](#description)

Converts a value into its JSON-format representation. Useful for debugging.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

The following component uses a JSON pipe to convert an object to JSON format, and displays the string in both formats for comparison.

```
@Component({
  selector: 'json-pipe',
  imports: [JsonPipe],
  template: `<div>
    <p>Without JSON pipe:</p>
    <pre>{{ object }}</pre>
    <p>With JSON pipe:</p>
    <pre>{{ object | json }}</pre>
  </div>`,
})
export class JsonPipeComponent {
  object: Object = {foo: 'bar', baz: 'qux', nested: {xyz: 3, numbers: [1, 2, 3, 4, 5]}};
}
```

Jump to details

----
url: https://angular.dev/guide/components/inheritance
----

Angular components are TypeScript classes and participate in standard JavaScript inheritance semantics.

A component can extend any base class:

```
export class ListboxBase {
  value: string;
}

@Component(/* ... */)
export class CustomListbox extends ListboxBase {
  // CustomListbox inherits the `value` property.
}
```

## [Extending other components and directives](#extending-other-components-and-directives)

When a component extends another component or a directive, it inherits some of the metadata defined in the base class's decorator and the base class's decorated members. This includes host bindings, inputs, outputs, lifecycle methods.

```
@Component({
  selector: 'base-listbox',
  template: ` ... `,
  host: {
    '(keydown)': 'handleKey($event)',
  },
})
export class ListboxBase {
  value = input.required<string>();
  handleKey(event: KeyboardEvent) {
    /* ... */
  }
}

@Component({
  selector: 'custom-listbox',
  template: ` ... `,
  host: {
    '(click)': 'focusActiveOption()',
  },
})
export class CustomListbox extends ListboxBase {
  disabled = input(false);
  focusActiveOption() {
    /* ... */
  }
}
```

In the example above, `CustomListbox` inherits all the information associated with `ListboxBase`, overriding the selector and template with its own values. `CustomListbox` has two inputs (`value` and `disabled`) and two event listeners (`keydown` and `click`).

Child classes end up with the *union* of all of their ancestors' inputs, outputs, and host bindings and their own.

### [Forwarding injected dependencies](#forwarding-injected-dependencies)

When a base class uses `inject()` as a property initializer, the child class inherits the property automatically. No `super` forwarding is needed.

```
@Component(/* ... */)
export class ListboxBase {
  protected element = inject(ElementRef);
}

@Component(/* ... */)
export class CustomListbox extends ListboxBase {
  // `element` is inherited from `ListboxBase`.
}
```

If a base class injects dependencies as constructor parameters, the child class must explicitly pass these dependencies to `super`.

```
@Component(/* ... */)
export class ListboxBase {
  constructor(private element: ElementRef) {}
}

@Component(/* ... */)
export class CustomListbox extends ListboxBase {
  constructor(element: ElementRef) {
    super(element);
  }
}
```

### [Overriding lifecycle methods](#overriding-lifecycle-methods)

If a base class defines a lifecycle method, such as `ngOnInit`, a child class that also implements `ngOnInit` *overrides* the base class's implementation. If you want to preserve the base class's lifecycle method, explicitly call the method with `super`:

```
@Component(/* ... */)
export class ListboxBase {
  protected isInitialized = false;
  ngOnInit() {
    this.isInitialized = true;
  }
}

@Component(/* ... */)
export class CustomListbox extends ListboxBase {
  override ngOnInit() {
    super.ngOnInit();
    /* ... */
  }
}
```

----
url: https://angular.dev/api/animations/AnimationAnimateChildMetadata
----

```
    
      interface AnimationAnimateChildMetadata extends AnimationMetadata {}
    
    
```

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/api/common/NgSwitchDefault
----

Use the `@default` block within a `@switch` block instead. Intent to remove in a future major release

## [API](#api)

```
    
      class NgSwitchDefault {}
    
    
```

@paramviewContainer`ViewContainerRef`

@paramtemplateRef`TemplateRef<Object>`

@paramngSwitch`NgSwitch`

@returns`NgSwitchDefault`

## [Description](#description)

Creates a view that is rendered when no [`NgSwitchCase`](/api/common/NgSwitchCase) expressions match the [`NgSwitch`](/api/common/NgSwitch) expression. This statement should be the final case in an [`NgSwitch`](/api/common/NgSwitch).

## [Exported by](#exported-by)

* `CommonModule`

Jump to details

----
url: https://angular.dev/api/common/ViewportScroller
----

## [API](/api/common/ViewportScroller#api)

```
    
      abstract class ViewportScroller {}
    
    
```

Configures the top offset used when scrolling to an anchor.

@paramoffset`[number, number] | (() => [number, number])`

A position in screen coordinates (a tuple with x and y values) or a function that returns the top offset position.

@returns`void`

Retrieves the current scroll position.

@returns`[number, number]`

A position in screen coordinates (a tuple with x and y values).

Scrolls to a specified position.

@paramposition`[number, number]`

A position in screen coordinates (a tuple with x and y values).

@paramoptions`ScrollOptions | undefined`

@returns`void`

Scrolls to an anchor element.

@paramanchor`string`

The ID of the anchor element.

@paramoptions`ScrollOptions | undefined`

Scroll options

@returns`void`

Disables automatic scroll restoration provided by the browser. See also [window.history.scrollRestoration info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration).

@paramscrollRestoration`"auto" | "manual"`

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/ApplicationInitStatus
----

```
    
      class ApplicationInitStatus {}
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/IS_ASYNC_VALIDATION_RESOURCE
----

```
    
      const IS_ASYNC_VALIDATION_RESOURCE: unique symbol;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/NgStyle
----

```
    
      class NgStyle implements DoCheck {}
    
    
```

@param\_ngEl`ElementRef<any>`

@param\_differs`KeyValueDiffers`

@param\_renderer`Renderer2`

@returns`NgStyle`

@returns`void`

## [Description](#description)

An attribute directive that updates styles for the containing HTML element. Sets one or more style properties, specified as colon-separated key-value pairs. The key is a style name, with an optional `.<unit>` suffix (such as 'top.px', 'font-style.em'). The value is an expression to be evaluated. The resulting non-null value, expressed in the given unit, is assigned to the given style property. If the result of evaluation is null, the corresponding style is removed.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

Set the width of the containing element to a pixel value returned by an expression.

```
<some-element [ngStyle]="{'max-width.px': widthExp}">...</some-element>
```

Set a collection of style values using an expression that returns key-value pairs.

```
<some-element [ngStyle]="objExp">...</some-element>
```

For more simple use cases you can use the [style bindings](/guide/templates/binding#css-class-and-style-property-bindings) directly. It doesn't require importing a directive.

Set the font of the containing element to the result of an expression.

```
<some-element [style]="{'font-style': styleExp}">...</some-element>
```

Jump to details

----
url: https://angular.dev/api/core/KeyValueDiffers
----

```
    
      class KeyValueDiffers {}
    
    
```

@paramfactories`KeyValueDifferFactory[]`

@returns`KeyValueDiffers`

@paramkv`any`

@returns`KeyValueDifferFactory`

@paramfactories`KeyValueDifferFactory[]`

@paramparent`KeyValueDiffers | undefined`

@returns`KeyValueDiffers`

Takes an array of [`KeyValueDifferFactory`](/api/core/KeyValueDifferFactory) and returns a provider used to extend the inherited [`KeyValueDiffers`](/api/core/KeyValueDiffers) instance with the provided factories and return a new [`KeyValueDiffers`](/api/core/KeyValueDiffers) instance.

@paramfactories`KeyValueDifferFactory[]`

@returns`StaticProvider`

Usage notes

### [Example](#example)

The following example shows how to extend an existing list of factories, which will only be applied to the injector for this component and its children. This step is all that's required to make a new [`KeyValueDiffer`](/api/core/KeyValueDiffer) available.

```
@Component({
  viewProviders: [
    KeyValueDiffers.extend([new ImmutableMapDiffer()])
  ]
})
```

Jump to details

----
url: https://angular.dev/api/core/TRANSLATIONS
----

```
    
      const TRANSLATIONS: InjectionToken<string>;
    
    
```

## [Description](#description)

Use this token at bootstrap to provide the content of your translation file (`xtb`, `xlf` or `xlf2`) when you want to translate your application in another language.

See the [i18n guide](guide/i18n/merge) for more information.

## [Usage Notes](#usage-notes)

### [Example](#example)

In standalone apps:

```
import { LOCALE_ID, ApplicationConfig } from '@angular/core';

const appConfig: ApplicationConfig = {
  providers: [{provide: TRANSLATIONS, useValue: translations }]
};
```

In module based apps:

```
import { TRANSLATIONS } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app.module';

// content of your translation file
const translations = '....';

platformBrowser().bootstrapModule(AppModule, {
  providers: [{provide: TRANSLATIONS, useValue: translations }]
});
```

Jump to details

----
url: https://angular.dev/api/forms/UntypedFormGroup
----

```
    
      type UntypedFormGroup = FormGroup<any>
    
    
```

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/5-control-flow-for
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/4-control-flow-if)[](/tutorials/learn-angular/6-property-binding)

[](/tutorials/learn-angular/4-control-flow-if)[](/tutorials/learn-angular/6-property-binding)

Often when building web applications, you need to repeat some code a specific number of times - for example, given an array of names, you may want to display each name in a `<p>` tag.

**NOTE:** Learn more about [control flow in the essentials guide](/essentials/templates#control-flow-with-if-and-for).

In this activity, you'll learn how to use `@for` to repeat elements in a template.

***

The syntax that enables repeating elements in a template is `@for`.

Here's an example of how to use the `@for` syntax in a component:

```
@Component({
  ...
  template: `
    @for (os of operatingSystems; track os.id) {
      {{ os.name }}
    }
  `,
})
export class App {
  operatingSystems = [{id: 'win', name: 'Windows'}, {id: 'osx', name: 'macOS'}, {id: 'linux', name: 'Linux'}];
}
```

Two things to take note of:

* There is an `@` prefix for the `for` because it is a special syntax called [Angular template syntax](guide/templates)
* For applications that still use `*ngFor`, please refer to the [Angular documentation for NgFor](guide/directives/structural-directives)

1. ### [Add the `users` property](#add-the-users-property)

   In the `App` class, add a property called `users` that contains users and their names.

   ```
   [
     {id: 0, name: 'Sarah'},
     {id: 1, name: 'Amy'},
     {id: 2, name: 'Rachel'},
     {id: 3, name: 'Jessica'},
     {id: 4, name: 'Poornima'},
   ];
   ```

2. ### [Update the template](#update-the-template)

   Update the template to display each user name in a `p` element using the `@for` template syntax.

   ```
   @for (user of users; track user.id) {
     <p>{{ user.name }}</p>
   }
   ```

   **NOTE:** the use of `track` is required, you may use the `id` or some other unique identifier.

This type of functionality is called control flow. Next, you'll learn to customize and communicate with components - by the way, you're doing a great job so far.

----
url: https://angular.dev/api/animations/style
----

```
function style(
  tokens:
    | '*'
    | { [key: string]: string | number }
    | ('*' | { [key: string]: string | number })[],
): AnimationStyleMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramtokens`"*" | { [key: string]: string | number; } | ("*" | { [key: string]: string | number; })[]`

A set of CSS styles or HTML styles associated with an animation state. The value can be any of the following:

* A key-value style pair associating a CSS property with a value.
* An array of key-value style pairs.
* An asterisk (\*), to use auto-styling, where styles are derived from the element being animated and applied to the animation when it starts.

Auto-styling can be used to define a state that depends on layout or other environmental factors.

@returns`AnimationStyleMetadata`

## [Usage Notes](#usage-notes)

The following examples create animation styles that collect a set of CSS property values:

```
// string values for CSS properties
style({ background: "red", color: "blue" })

// numerical pixel values
style({ width: 100, height: 0 })
```

The following example uses auto-styling to allow an element to animate from a height of 0 up to its full height:

```
style({ height: 0 }),
animate("1s", style({ height: "*" }))
```

Jump to details

----
url: https://angular.dev/api/core/DefaultIterableDiffer
----

v4.0.0 - Should not be part of public API.

## [API](#api)

```
    
      class DefaultIterableDiffer<V> implements IterableDiffer<V> ,IterableChanges<V> {}
    
    
```

@paramtrackByFn`TrackByFunction<V> | undefined`

@returns`DefaultIterableDiffer<V>`

@paramfn`(record: IterableChangeRecord_<V>) => void`

@returns`void`

@paramfn`(item: IterableChangeRecord<V>, previousIndex: number | null, currentIndex: number | null) => void`

@returns`void`

@paramfn`(record: IterableChangeRecord_<V>) => void`

@returns`void`

@paramfn`(record: IterableChangeRecord_<V>) => void`

@returns`void`

@paramfn`(record: IterableChangeRecord_<V>) => void`

@returns`void`

@paramfn`(record: IterableChangeRecord_<V>) => void`

@returns`void`

@paramfn`(record: IterableChangeRecord_<V>) => void`

@returns`void`

@paramcollection`NgIterable<V> | null | undefined`

@returns`DefaultIterableDiffer<V> | null`

@returns`void`

@paramcollection`NgIterable<V>`

@returns`boolean`

Jump to details

----
url: https://angular.dev/api/router/InitialNavigationFeature
----

```
    
      type InitialNavigationFeature = EnabledBlockingInitialNavigationFeature | DisabledInitialNavigationFeature
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/FormGroup
----

```
    
      class FormGroup<TControl extends {[K in keyof TControl]: AbstractControl<any>} = any> extends AbstractControl<  ɵTypedOrUntyped<TControl, ɵFormGroupValue<TControl>, any>,  ɵTypedOrUntyped<TControl, ɵFormGroupRawValue<TControl>, any>,  ɵTypedOrUntyped<TControl, ɵFormGroupArgumentValue<TControl>, any>> {
}
    
    
```

Creates a new [`FormGroup`](/api/forms/FormGroup) instance.

@paramcontrols`TControl`

A collection of child controls. The key for each child is the name under which it is registered.

@paramvalidatorOrOpts`ValidatorFn | AbstractControlOptions | ValidatorFn[] | null | undefined`

A synchronous validator function, or an array of such functions, or an [`AbstractControlOptions`](/api/forms/AbstractControlOptions) object that contains validation functions and a validation trigger.

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

A single async validator or array of async validator functions

@returns`FormGroup<TControl>`

Registers a control with the group's list of controls. In a strongly-typed group, the control must be in the group's type (possibly as an optional key).

This method does not update the value or validity of the control. Use [`addControl`](/api/forms/FormGroup#addControl) instead.

@paramname`K`

The control name to register in the collection

@paramcontrol`TControl[K]`

Provides the control for the given name

@returns`TControl[K]`

@paramthis`FormGroup<{ [key: string]: AbstractControl<any, any, any>; }>`

@paramname`string`

@paramcontrol`AbstractControl<any, any, any>`

@returns`AbstractControl<any, any, any>`

Add a control to this group. In a strongly-typed group, the control must be in the group's type (possibly as an optional key).

If a control with a given name already exists, it would *not* be replaced with a new one. If you want to replace an existing control, use the [`setControl`](/api/forms/FormGroup#setControl) method instead. This method also updates the value and validity of the control.

@paramthis`FormGroup<{ [key: string]: AbstractControl<any, any, any>; }>`

@paramname`string`

The control name to add to the collection

@paramcontrol`AbstractControl<any, any, any>`

Provides the control for the given name

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

Specifies whether this FormGroup instance should emit events after a new control is added.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is added. When false, no events are emitted.

@returns`void`

@paramname`K`

@paramcontrol`Required<TControl>[K]`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

@paramthis`FormGroup<{ [key: string]: AbstractControl<any, any, any>; }>`

@paramname`string`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

@paramname`ɵOptionalKeys<TControl> & S`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Replace an existing control. In a strongly-typed group, the control must be in the group's type (possibly as an optional key).

If a control with a given name does not exist in this [`FormGroup`](/api/forms/FormGroup), it will be added.

@paramname`K`

The control name to replace in the collection

@paramcontrol`TControl[K]`

Provides the control for the given name

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

Specifies whether this FormGroup instance should emit events after an existing control is replaced.

* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is replaced with a new one. When false, no events are emitted.

@returns`void`

@paramthis`FormGroup<{ [key: string]: AbstractControl<any, any, any>; }>`

@paramname`string`

@paramcontrol`AbstractControl<any, any, any>`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Check whether there is an enabled control with the given name in the group.

Reports false for disabled controls. If you'd like to check for existence in the group only, use [`get`](/api/forms/AbstractControl#get) instead.

@paramcontrolName`K`

The control name to check for existence in the collection

@returns`boolean`

false for disabled controls, true otherwise.

@paramthis`FormGroup<{ [key: string]: AbstractControl<any, any, any>; }>`

@paramcontrolName`string`

@returns`boolean`

Sets the value of the [`FormGroup`](/api/forms/FormGroup). It accepts an object that matches the structure of the group, with control names as keys.

@paramvalue`ɵIsAny<TControl, { [key: string]: any; }, { [K in keyof TControl]: ɵRawValue<TControl[K]>; }>`

The new value for the control that matches the structure of the group.

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; }`

Configuration options that determine how the control propagates changes and emits events after the value changes. The configuration options are passed to the [`* updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control value is updated. When false, no events are emitted.

@returns`void`

Usage notes

### [Set the complete value for the form group](#set-the-complete-value-for-the-form-group)

```
const form = new FormGroup({
  first: new FormControl(),
  last: new FormControl()
});

console.log(form.value);   // {first: null, last: null}

form.setValue({first: 'Nancy', last: 'Drew'});
console.log(form.value);   // {first: 'Nancy', last: 'Drew'}
```

Patches the value of the [`FormGroup`](/api/forms/FormGroup). It accepts an object with control names as keys, and does its best to match the values to the correct controls in the group.

It accepts both super-sets and sub-sets of the group without throwing an error.

@paramvalue`ɵIsAny<TControl, { [key: string]: any; }, Partial<{ [K in keyof TControl]: ɵValue<TControl[K]>; }>>`

The object that matches the structure of the group.

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; }`

Configuration options that determine how the control propagates changes and emits events after the value is patched.

* `onlySelf`: When true, each change only affects this control and not its parent. Default is true.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control value is updated. When false, no events are emitted. The configuration options are passed to the [`updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

@returns`void`

Usage notes

### [Patch the value for a form group](#patch-the-value-for-a-form-group)

```
const form = new FormGroup({
   first: new FormControl(),
   last: new FormControl()
});
console.log(form.value);   // {first: null, last: null}

form.patchValue({first: 'Nancy'});
console.log(form.value);   // {first: 'Nancy', last: null}
```

Resets the [`FormGroup`](/api/forms/FormGroup), marks all descendants `pristine` and `untouched` and sets the value of all descendants to their default values, or null if no defaults were provided.

You reset to a specific form state by passing in a map of states that matches the structure of your form, with control names as keys. The state is a standalone value or a form state object with both a value and a disabled status.

@paramvalue`ɵTypedOrUntyped<TControl, ɵIsAny<TControl, { [key: string]: any; }, Partial<{ [K in keyof TControl]: ɵValue<TControl[K]> | FormControlState<ɵValue<TControl[K]>>; }>>, any>`

Resets the control with an initial value, or an object that defines the initial value and disabled state.

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; overwriteDefaultValue?: boolean | undefined; }`

Configuration options that determine how the control propagates changes and emits events when the group is reset.

* `onlySelf`: When true, each change only affects this control, and not its parent. Default is false.
* `emitEvent`: When true or not supplied (the default), both the `statusChanges` and `valueChanges` observables emit events with the latest status and value when the control is reset. When false, no events are emitted. The configuration options are passed to the [`* updateValueAndValidity`](/api/forms/AbstractControl#updateValueAndValidity) method.

@returns`void`

Usage notes

### [Reset the form group values](#reset-the-form-group-values)

```
const form = new FormGroup({
  first: new FormControl('first name'),
  last: new FormControl('last name')
});

console.log(form.value);  // {first: 'first name', last: 'last name'}

form.reset({ first: 'name', last: 'last name' });

console.log(form.value);  // {first: 'name', last: 'last name'}
```

### [Reset the form group values and disabled status](#reset-the-form-group-values-and-disabled-status)

```
const form = new FormGroup({
  first: new FormControl('first name'),
  last: new FormControl('last name')
});

form.reset({
  first: {value: 'name', disabled: true},
  last: 'last'
});

console.log(form.value);  // {last: 'last'}
console.log(form.get('first').status);  // 'DISABLED'
```

The aggregate value of the [`FormGroup`](/api/forms/FormGroup), including any disabled controls.

Retrieves all values regardless of disabled status.

@returns`ɵTypedOrUntyped<TControl, ɵIsAny<TControl, { [key: string]: any; }, { [K in keyof TControl]: ɵRawValue<TControl[K]>; }>, any>`

```
// Reference to the RequiredValidator
const ctrl = new FormControl<string | null>('', Validators.required);
ctrl.removeValidators(Validators.required);

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<string | null>('', minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)

ctrl.removeValidators(minValidator);
```

```
// Reference to the RequiredValidator
const ctrl = new FormControl<number | null>(0, Validators.required);
expect(ctrl.hasValidator(Validators.required)).toEqual(true)

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<number | null>(0, minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)
```

```
const login = new FormControl('someLogin');
login.setErrors({
  notUnique: true
});

expect(login.valid).toEqual(false);
expect(login.errors).toEqual({ notUnique: true });

login.setValue('someOtherLogin');

expect(login.valid).toEqual(true);
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

Tracks the value and validity state of a group of [`FormControl`](/api/forms/FormControl) instances.

A [`FormGroup`](/api/forms/FormGroup) aggregates the values of each child [`FormControl`](/api/forms/FormControl) into one object, with each control name as the key. It calculates its status by reducing the status values of its children. For example, if one of the controls in a group is invalid, the entire group becomes invalid.

[`FormGroup`](/api/forms/FormGroup) is one of the four fundamental building blocks used to define forms in Angular, along with [`FormControl`](/api/forms/FormControl), [`FormArray`](/api/forms/FormArray), and [`FormRecord`](/api/forms/FormRecord).

When instantiating a [`FormGroup`](/api/forms/FormGroup), pass in a collection of child controls as the first argument. The key for each child registers the name for the control.

[`FormGroup`](/api/forms/FormGroup) is intended for use cases where the keys are known ahead of time. If you need to dynamically add and remove controls, use [`FormRecord`](/api/forms/FormRecord) instead.

[`FormGroup`](/api/forms/FormGroup) accepts an optional type parameter `TControl`, which is an object type with inner control types as values.

## [Usage Notes](#usage-notes)

### [Create a form group with 2 controls](#create-a-form-group-with-2-controls)

```
const form = new FormGroup({
  first: new FormControl('Nancy', Validators.minLength(2)),
  last: new FormControl('Drew'),
});

console.log(form.value);   // {first: 'Nancy', last; 'Drew'}
console.log(form.status);  // 'VALID'
```

### [The type argument, and optional controls](#the-type-argument-and-optional-controls)

[`FormGroup`](/api/forms/FormGroup) accepts one generic argument, which is an object containing its inner controls. This type will usually be inferred automatically, but you can always specify it explicitly if you wish.

If you have controls that are optional (i.e. they can be removed, you can use the `?` in the type):

```
const form = new FormGroup<{
  first: FormControl<string|null>,
  middle?: FormControl<string|null>, // Middle name is optional.
  last: FormControl<string|null>,
}>({
  first: new FormControl('Nancy'),
  last: new FormControl('Drew'),
});
```

### [Create a form group with a group-level validator](#create-a-form-group-with-a-group-level-validator)

You include group-level validators as the second arg, or group-level async validators as the third arg. These come in handy when you want to perform validation that considers the value of more than one child control.

```
const form = new FormGroup({
  password: new FormControl('', Validators.minLength(2)),
  passwordConfirm: new FormControl('', Validators.minLength(2)),
}, passwordMatchValidator);


function passwordMatchValidator(g: FormGroup) {
   return g.get('password').value === g.get('passwordConfirm').value
      ? null : {'mismatch': true};
}
```

Like [`FormControl`](/api/forms/FormControl) instances, you choose to pass in validators and async validators as part of an options object.

```
const form = new FormGroup({
  password: new FormControl('')
  passwordConfirm: new FormControl('')
}, { validators: passwordMatchValidator, asyncValidators: otherValidator });
```

### [Set the updateOn property for all controls in a form group](#set-the-updateon-property-for-all-controls-in-a-form-group)

The options object is used to set a default value for each child control's `updateOn` property. If you set `updateOn` to `'blur'` at the group level, all child controls default to 'blur', unless the child has explicitly specified a different `updateOn` value.

```
const c = new FormGroup({
  one: new FormControl()
}, { updateOn: 'blur' });
```

### [Using a FormGroup with optional controls](#using-a-formgroup-with-optional-controls)

It is possible to have optional controls in a FormGroup. An optional control can be removed later using `removeControl`, and can be omitted when calling `reset`. Optional controls must be declared optional in the group's type.

```
const c = new FormGroup<{one?: FormControl<string>}>({
  one: new FormControl('')
});
```

Notice that `c.value.one` has type `string|null|undefined`. This is because calling `c.reset({})` without providing the optional key `one` will cause it to become `null`.

Jump to details

----
url: https://angular.dev/api/router/ActivatedRoute
----

```
    
      class ActivatedRoute {}
    
    
```

@paramurlSubject`BehaviorSubject<UrlSegment[]>`

@paramparamsSubject`BehaviorSubject<Params>`

@paramqueryParamsSubject`BehaviorSubject<Params>`

@paramfragmentSubject`BehaviorSubject<string | null>`

@paramdataSubject`BehaviorSubject<Data>`

@paramoutlet`string`

The outlet name of the route, a constant.

@paramcomponent`Type<any> | null`

The component of the route, a constant.

@paramfutureSnapshot`ActivatedRouteSnapshot`

@returns`ActivatedRoute`

The current snapshot of this route

An Observable of the resolved route title

An observable of the URL segments matched by this route.

An observable of the matrix parameters scoped to this route.

An observable of the query parameters shared by all the routes.

An observable of the URL fragment shared by all the routes.

An observable of the static and resolved data of this route.

A map of resources for this route.

The outlet name of the route, a constant.

The component of the route, a constant.

The configuration used to match this route.

The root of the router state.

The parent of this route in the router state tree.

The first child of this route in the router state tree.

The children of this route in the router state tree.

The path from the root of the router state tree to this route.

An Observable that contains a map of the required and optional parameters specific to the route. The map supports retrieving single and multiple values from the same parameter.

An Observable that contains a map of the query parameters available to all routes. The map supports retrieving single and multiple values from the query parameter.

@returns`string`

## [Description](#description)

Provides access to information about a route associated with a component that is loaded in an outlet. Use to traverse the [`RouterState`](/api/router/RouterState) tree and extract information from nodes.

The following example shows how to construct a component using information from a currently activated route.

Note: the observables in this class only emit when the current and previous values differ based on shallow equality. For example, changing deeply nested properties in resolved `data` will not cause the [`ActivatedRoute.data`](/api/router/ActivatedRoute#data) `Observable` to emit a new value.

```
import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {map, Observable} from 'rxjs';
@Component({
})
export class ActivatedRouteComponent {
  constructor(route: ActivatedRoute) {
    const id: Observable<string> = route.params.pipe(map((p) => p['id']));
    const url: Observable<string> = route.url.pipe(map((segments) => segments.join('')));
    // route.data includes both `data` and `resolve`
    const user = route.data.pipe(map((d) => d['user']));
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/SimpleChanges
----

```
    
      type SimpleChanges<T = unknown> = T extends object  ? {      [Key in keyof T]?: SimpleChange<        T[Key] extends {[ɵINPUT_SIGNAL_BRAND_READ_TYPE]: infer V} ? V : T[Key]      >;    }  : {      [propName: string]: SimpleChange; // Backwards-compatible signature.    }
    
    
```

Jump to details

----
url: https://angular.dev/api/core/createEnvironmentInjector
----

```
function createEnvironmentInjector(
  providers: (Provider | EnvironmentProviders)[],
  parent: EnvironmentInjector,
  debugName?: string | null,
): EnvironmentInjector;
```

@paramproviders`(Provider | EnvironmentProviders)[]`

An array of providers.

@paramparent`EnvironmentInjector`

A parent environment injector.

@paramdebugName`string | null`

An optional name for this injector instance, which will be used in error messages.

@returns`EnvironmentInjector`

Jump to details

----
url: https://angular.dev/api/forms/NgSelectOption
----

```
    
      class NgSelectOption implements OnDestroy {}
    
    
```

@param\_element`ElementRef<any>`

@param\_renderer`Renderer2`

@param\_select`SelectControlValueAccessor`

@returns`NgSelectOption`

ID of the option element

Tracks the value bound to the option element. Unlike the value binding, ngValue supports binding to objects.

Tracks simple string values bound to the option element. For objects, use the `ngValue` input binding.

## [Description](#description)

Marks `<option>` as dynamic, so Angular can be notified when options change.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

Jump to details

----
url: https://angular.dev/api/router/RedirectFunction
----

```
    
      type RedirectFunction = (  redirectData: PartialMatchRouteSnapshot,) => MaybeAsync<string | UrlTree>
    
    
```

## [Description](#description)

The type for the function that can be used to handle redirects when the path matches a [`Route`](/api/router/Route) config.

The [`RedirectFunction`](/api/router/RedirectFunction) does not have access to the full [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) interface because Route matching has not yet completed when the function is called. See [`PartialMatchRouteSnapshot`](/api/router/PartialMatchRouteSnapshot) for more information.

Jump to details

----
url: https://angular.dev/guide/templates/event-listeners
----

Angular supports defining event listeners on an element in your template by specifying the event name inside parentheses along with a statement that runs every time the event occurs.

## [Listening to native events](#listening-to-native-events)

When you want to add event listeners to an HTML element, you wrap the event with parentheses, `()`, which allows you to specify a listener statement.

```
@Component({
  template: `
    <input type="text" (keyup)="updateField()" />
  `,
  ...
})
export class App{
  updateField(): void {
    console.log('Field is updated!');
  }
}
```

In this example, Angular calls `updateField` every time the `<input>` element emits a `keyup` event.

You can add listeners for any native events, such as: `click`, `keydown`, `mouseover`, etc. To learn more, check out the [all available events on elements on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element#events).

## [Accessing the event argument](#accessing-the-event-argument)

In every template event listener, Angular provides a variable named `$event` that contains a reference to the event object.

```
@Component({
  template: `
    <input type="text" (keyup)="updateField($event)" />
  `,
  ...
})
export class App {
  updateField(event: KeyboardEvent): void {
    console.log(`The user pressed: ${event.key}`);
  }
}
```

## [Using key modifiers](#using-key-modifiers)

When you want to capture specific keyboard events for a specific key, you might write some code like the following:

```
@Component({
  template: `
    <input type="text" (keyup)="updateField($event)" />
  `,
  ...
})
export class App {
  updateField(event: KeyboardEvent): void {
    if (event.key === 'Enter') {
      console.log('The user pressed enter in the text field.');
    }
  }
}
```

However, since this is a common scenario, Angular lets you filter the events by specifying a specific key using the period (`.`) character. By doing so, code can be simplified to:

```
@Component({
  template: `
    <input type="text" (keyup.enter)="updateField($event)" />
  `,
  ...
})
export class App{
  updateField(event: KeyboardEvent): void {
    console.log('The user pressed enter in the text field.');
  }
}
```

You can also add additional key modifiers:

```
<!-- Matches shift and enter -->
<input type="text" (keyup.shift.enter)="updateField($event)" />
```

Angular supports the modifiers `alt`, `control`, `meta`, and `shift`.

You can specify the key or code that you would like to bind to keyboard events. The key and code fields are a native part of the browser keyboard event object. By default, event binding assumes you want to use the [Key values for keyboard events](https://developer.mozilla.org/docs/Web/API/UI_Events/Keyboard_event_key_values).

Angular also allows you to specify [Code values for keyboard events](https://developer.mozilla.org/docs/Web/API/UI_Events/Keyboard_event_code_values) by providing a built-in `code` suffix.

```
<!-- Matches alt and left shift -->
<input type="text" (keydown.code.alt.shiftleft)="updateField($event)" />
```

This can be useful for handling keyboard events consistently across different operating systems. For example, when using the Alt key on macOS devices, the `key` property reports the key based on the character already modified by the Alt key. This means that a combination like Alt + S reports a `key` value of `'ß'`. The `code` property, however, corresponds to the physical or virtual button pressed rather than the character produced.

## [Listening on global targets](#listening-on-global-targets)

Global target names can be used to prefix an event. The 3 supported global targets are `window`, `document` and `body`.

```
@Component({
  /* ... */
  host: {
    '(window:click)': 'onWindowClick()',
    '(document:click)': 'onDocumentClick()',
    '(body:click)': 'onBodyClick()',
  },
})
export class MyView {}
```

## [Preventing event default behavior](#preventing-event-default-behavior)

If your event handler should replace the native browser behavior, you can use the event object's [`preventDefault` method](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault):

```
@Component({
  template: `
    <a href="#overlay" (click)="showOverlay($event)">
  `,
  ...
})
export class App{
  showOverlay(event: PointerEvent): void {
    event.preventDefault();
    console.log('Show overlay without updating the URL!');
  }
}
```

If the event handler statement evaluates to `false`, Angular automatically calls `preventDefault()`, similar to [native event handler attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes#event_handler_attributes). *Always prefer explicitly calling `preventDefault`*, as this approach makes the code's intent obvious.

## [Extend event handling](#extend-event-handling)

Angular’s event system is extensible via custom event plugins registered with the [`EVENT_MANAGER_PLUGINS`](/api/platform-browser/EVENT_MANAGER_PLUGINS) injection token.

### [Implementing Event Plugin](#implementing-event-plugin)

To create a custom event plugin, extend the [`EventManagerPlugin`](/api/platform-browser/EventManagerPlugin) class and implement the required methods.

```
import {Injectable} from '@angular/core';
import {EventManagerPlugin} from '@angular/platform-browser';

@Injectable()
export class DebounceEventPlugin extends EventManagerPlugin {
  constructor() {
    super(document);
  }

  // Define which events this plugin supports
  override supports(eventName: string) {
    return /debounce/.test(eventName);
  }

  // Handle the event registration
  override addEventListener(element: HTMLElement, eventName: string, handler: Function) {
    // Parse the event: e.g., "click.debounce.500"
    // event: "click", delay: 500
    const [event, , delay = '300'] = eventName.split('.');

    let timeoutId: ReturnType<typeof setTimeout>;

    const listener = (e: Event) => {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        handler(e);
      }, Number(delay));
    };

    element.addEventListener(event, listener);

    // Return cleanup function
    return () => {
      clearTimeout(timeoutId);
      element.removeEventListener(event, listener);
    };
  }
}
```

Register your custom plugin using the [`EVENT_MANAGER_PLUGINS`](/api/platform-browser/EVENT_MANAGER_PLUGINS) token in your application's providers:

```
import {bootstrapApplication, EVENT_MANAGER_PLUGINS} from '@angular/platform-browser';
import {App} from './app';
import {DebounceEventPlugin} from './debounce-event-plugin';

bootstrapApplication(App, {
  providers: [
    {
      provide: EVENT_MANAGER_PLUGINS,
      useClass: DebounceEventPlugin,
      multi: true,
    },
  ],
});
```

Once registered, you can use your custom event syntax in templates, as well as with the `host` property:

```
@Component({
  template: `
    <input
      type="text"
      (input.debounce.500)="onSearch($event.target.value)"
      placeholder="Search..."
    />
  `,
  ...
})
export class Search {
 onSearch(query: string): void {
    console.log('Searching for:', query);
  }
}
```

```
@Component({
  ...,
  host: {
    '(click.debounce.500)': 'handleDebouncedClick()',
  },
})
export class AwesomeCard {
  handleDebouncedClick(): void {
   console.log('Debounced click!');
  }
}
```

----
url: https://angular.dev/api/core/InjectableType
----

```
    
      interface InjectableType<T> extends Type<T> {}
    
    
```

A [`Type`](/api/core/Type) which has a `ɵprov: ɵɵInjectableDeclaration` static field.

[`InjectableType`](/api/core/InjectableType)s contain their own Dependency Injection metadata and are usable in an `InjectorDef`-based `StaticInjector`.

Jump to details

----
url: https://angular.dev/cli/generate/web-worker
----

```
ng generate web-worker [name] [options]
```

Creates a new web worker in your project. Web workers allow you to run JavaScript code in the background, improving the performance and responsiveness of your application by offloading computationally intensive tasks. This schematic generates the necessary files for a new web worker and provides an optional code snippet to demonstrate its usage.

## [Arguments](#arguments)

`name`

The name for the new web worker. This will be used to create the worker file (e.g., `my-worker.worker.ts`).

Value Type`string`

## [Options](#options)

`project`

The name of the project where the web worker should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`snippet`

Generate a code snippet that demonstrates how to create and use the new web worker.

Value Type`boolean`Default`true`

Jump to details

----
url: https://angular.dev/reference/migrations/standalone
----

**Standalone components** provide a simplified way to build Angular applications. Standalone components, directives, and pipes aim to streamline the authoring experience by reducing the need for [`NgModule`](/api/core/NgModule)s. Existing applications can optionally and incrementally adopt the new standalone style without any breaking changes.

[](https://www.youtube.com/watch?v=x5PZwb4XurU\&autoplay=1)

This schematic helps to transform components, directive and pipes in existing projects to become standalone. The schematic aims to transform as much code as possible automatically, but it may require some manual fixes by the project author.

Run the schematic using the following command:

```
ng generate @angular/core:standalone
```

## [Before updating](#before-updating)

Before using the schematic, please ensure that the project:

1. Is using Angular 15.2.0 or later.
2. Builds without any compilation errors.
3. Is on a clean Git branch and all work is saved.

## [Schematic options](#schematic-options)

| Option | Details                                                                                                                       |
| :----- | :---------------------------------------------------------------------------------------------------------------------------- |
| `mode` | The transformation to perform. See [Migration modes](#migration-modes) below for details on the available options.            |
| `path` | The path to migrate, relative to the project root. You can use this option to migrate sections of your project incrementally. |

## [Migrations steps](#migrations-steps)

The migration process is composed of three steps. You'll have to run it multiple times and check manually that the project builds and behaves as expected.

**NOTE:** While the schematic can automatically update most code, some edge cases require developer intervention. You should plan to apply manual fixes after each step of the migration. Additionally, the new code generated by the schematic may not match your code's formatting rules.

Run the migration in the order listed below, verifying that your code builds and runs between each step:

1. Run `ng g @angular/core:standalone` and select "Convert all components, directives and pipes to standalone"
2. Run `ng g @angular/core:standalone` and select "Remove unnecessary NgModule classes"
3. Run `ng g @angular/core:standalone` and select "Bootstrap the application using standalone APIs"
4. Run any linting and formatting checks, fix any failures, and commit the result

## [After the migration](#after-the-migration)

Congratulations, your application has been converted to standalone 🎉. These are some optional follow-up steps you may want to take now:

* Find and remove any remaining [`NgModule`](/api/core/NgModule) declarations: since the ["Remove unnecessary NgModules" step](#remove-unnecessary-ngmodules) cannot remove all modules automatically, you may have to remove the remaining declarations manually.
* Run the project's unit tests and fix any failures.
* Run any code formatters, if the project uses automatic formatting.
* Run any linters in your project and fix new warnings. Some linters support a `--fix` flag that may resolve some of your warnings automatically.

## [Migration modes](#migration-modes)

The migration has the following modes:

1. Convert declarations to standalone.
2. Remove unnecessary NgModules.
3. Switch to standalone bootstrapping API. You should run these migrations in the order given.

### [Convert declarations to standalone](#convert-declarations-to-standalone)

In this mode, the migration converts all components, directives and pipes to standalone by removing `standalone: false` and adding dependencies to their `imports` array.

**HELPFUL:** The schematic ignores NgModules which bootstrap a component during this step because they are likely root modules used by `bootstrapModule` rather than the standalone-compatible [`bootstrapApplication`](/api/platform-browser/bootstrapApplication). The schematic converts these declarations automatically as a part of the ["Switch to standalone bootstrapping API"](#switch-to-standalone-bootstrapping-api) step.

**Before:**

### shared.module.ts

```
@NgModule({
  imports: [CommonModule],
  declarations: [Greeter],
  exports: [Greeter],
})
export class SharedModule {}
```

### greeter.ts

```
@Component({
  selector: 'greeter',
  template: '<div *ngIf="showGreeting">Hello</div>',
  standalone: false,
})
export class Greeter {
  showGreeting = true;
}
```

**After:**

### shared.module.ts

```
@NgModule({
  imports: [CommonModule, Greeter],
  exports: [Greeter],
})
export class SharedModule {}
```

### greeter.ts

```
@Component({
  selector: 'greeter',
  template: '<div *ngIf="showGreeting">Hello</div>',
  imports: [NgIf],
})
export class Greeter {
  showGreeting = true;
}
```

### [Remove unnecessary NgModules](#remove-unnecessary-ngmodules)

After converting all declarations to standalone, many NgModules can be safely removed. This step deletes such module declarations and as many corresponding references as possible. If the migration cannot delete a reference automatically, it leaves the following TODO comment so that you can delete the NgModule manually:

```
/* TODO(standalone-migration): clean up removed NgModule reference manually */
```

The migration considers a module safe to remove if that module:

* Has no `declarations`.
* Has no `providers`.
* Has no `bootstrap` components.
* Has no `imports` that reference a [`ModuleWithProviders`](/api/core/ModuleWithProviders) symbol or a module that can't be removed.
* Has no class members. Empty constructors are ignored.

**Before:**

### importer.module.ts

```
@NgModule({
  imports: [FooComponent, BarPipe],
  exports: [FooComponent, BarPipe],
})
export class ImporterModule {}
```

**After:**

### importer.module.ts

```
// Does not exist!
```

### [Switch to standalone bootstrapping API](#switch-to-standalone-bootstrapping-api)

This step converts any usages of `bootstrapModule` to the new, standalone-based [`bootstrapApplication`](/api/platform-browser/bootstrapApplication). It also removes `standalone: false` from the root component and deletes the root NgModule. If the root module has any `providers` or `imports`, the migration attempts to copy as much of this configuration as possible into the new bootstrap call.

**Before:**

### ./app/app.module.ts

```
import {NgModule} from '@angular/core';
import {App} from './app';

@NgModule({
  declarations: [App],
  bootstrap: [App],
})
export class AppModule {}
```

### ./app/app.ts

```
@Component({
  selector: 'app',
  template: 'hello',
  standalone: false,
})
export class App {}
```

### ./main.ts

```
import {platformBrowser} from '@angular/platform-browser';
import {AppModule} from './app/app.module';

platformBrowser()
  .bootstrapModule(AppModule)
  .catch((e) => console.error(e));
```

**After:**

### ./app/app.module.ts

```
// Does not exist!
```

### ./app/app.ts

```
@Component({
  selector: 'app',
  template: 'hello',
})
export class App {}
```

### ./main.ts

```
import {bootstrapApplication} from '@angular/platform-browser';
import {App} from './app/app';

bootstrapApplication(App).catch((e) => console.error(e));
```

## [Common problems](#common-problems)

Some common problems that may prevent the schematic from working correctly include:

* Compilation errors - if the project has compilation errors, Angular cannot analyze and migrate it correctly.
* Files not included in a tsconfig - the schematic determines which files to migrate by analyzing your project's `tsconfig.json` files. The schematic excludes any files not captured by a tsconfig.
* Code that cannot be statically analyzed - the schematic uses static analysis to understand your code and determine where to make changes. The migration may skip any classes with metadata that cannot be statically analyzed at build time.

## [Limitations](#limitations)

Due to the size and complexity of the migration, there are some cases that the schematic cannot handle:

* Because unit tests are not ahead-of-time (AoT) compiled, `imports` added to components in unit tests might not be entirely correct.
* The schematic relies on direct calls to Angular APIs. The schematic cannot recognize custom wrappers around Angular APIs. For example, if there you define a custom `customConfigureTestModule` function that wraps [`TestBed.configureTestingModule`](/api/core/testing/TestBed#configureTestingModule), components it declares may not be recognized.

----
url: https://angular.dev/tutorials/deferrable-views/3-defer-triggers
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/deferrable-views/2-loading-error-placeholder)

[](/tutorials/deferrable-views/2-loading-error-placeholder)

While the default options for `@defer` offer great options for lazy loading parts of your components it may still be desirable to further customize the deferred loading experience.

By default, deferred content loads when the browser is idle. You can, however, customize when this loading occurs by specifying a **trigger**. This lets you pick the loading behavior best suited to your component.

Deferrable views offer two types of loading trigger:

| Trigger | Description                                                                                                                                                                                                |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `on`    | A trigger condition using a trigger from the list of built-in triggers. For example: `@defer (on viewport)`                                                                                                |
| `when`  | A condition as an expression which is evaluated for truthiness. When the expression is truthy, the placeholder is swapped with the lazily loaded content. For example: `@defer (when customizedCondition)` |

If the `when` condition evaluates to `false`, the `defer` block is not reverted back to the placeholder. The swap is a one-time operation.

You can define multiple event triggers at once, these triggers will be evaluated as OR conditions.

* Ex: `@defer (on viewport; on timer(2s))`
* Ex: `@defer (on viewport; when customizedCondition)`

In this activity, you'll learn how to use triggers to specify the condition to load the deferrable views.

***

1. ### [Add `on hover` trigger](#add-on-hover-trigger)

   In your `app.ts`, add an `on hover` trigger to the `@defer` block.

   ```
   @defer (on hover) {
     <article-comments />
   } @placeholder (minimum 1s) {
     <p>Placeholder for comments</p>
   } @loading (minimum 1s; after 500ms) {
     <p>Loading comments...</p>
   } @error {
     <p>Failed to load comments</p>
   }
   ```

   Now, the page will not render the comments section until you hover its placeholder.

2. ### [Add a 'Show all comments' button](#add-a-show-all-comments-button)

   Next, update the template to include a button with the label "Show all comments". Include a template variable called `#showComments` with the button.

   ```
   <button type="button" #showComments>Show all comments</button>

   @defer (on hover) {
     <article-comments />
   } @placeholder (minimum 1s) {
     <p>Placeholder for comments</p>
   } @loading (minimum 1s; after 500ms) {
     <p>Loading comments...</p>
   } @error {
     <p>Failed to load comments</p>
   }
   ```

   **NOTE:** for more information on [template variables check the documentation](/guide/templates/variables#declaring-a-template-reference-variable).

3. ### [Add `on interaction` trigger](#add-on-interaction-trigger)

   Update the `@defer` block in the template to use the `on interaction` trigger. Provide the `showComments` template variable as the parameter to `interaction`.

   ```
   <button type="button" #showComments>Show all comments</button>

   @defer (on hover; on interaction(showComments)) {
     <article-comments />
   } @placeholder (minimum 1s) {
     <p>Placeholder for comments</p>
   } @loading (minimum 1s; after 500ms) {
     <p>Loading comments...</p>
   } @error {
     <p>Failed to load comments</p>
   }
   ```

   With these changes, the page will wait for one of the following conditions before rendering the comments section:

   * User hovers the comments section’s placeholder
   * User clicks on the “Show all comments" button

   You can reload the page to try out different triggers to render the comments section.

If you would like to learn more, check out the documentation for [Deferrable View](/guide/templates/defer). Keep learning to unlock more of Angular's great features.

----
url: https://angular.dev/api/common/isPlatformBrowser
----

```
function isPlatformBrowser(platformId: Object): boolean;
```

@paramplatformId`Object`

@returns`boolean`

Jump to details

----
url: https://angular.dev/api/forms/Form
----

```
    
      interface Form {}
    
    
```

Add a control to this form.

@paramdir`NgControl`

The control directive to add to the form.

@returns`void`

Remove a control from this form.

@paramdir`NgControl`

: The control directive to remove from the form.

@returns`void`

The control directive from which to get the [`FormControl`](/api/forms/FormControl).

@paramdir`NgControl`

: The control directive.

@returns`FormControl<any>`

Add a group of controls to this form.

@paramdir`AbstractFormGroupDirective`

: The control group directive to add.

@returns`void`

Remove a group of controls to this form.

@paramdir`AbstractFormGroupDirective`

: The control group directive to remove.

@returns`void`

The [`FormGroup`](/api/forms/FormGroup) associated with a particular [`AbstractFormGroupDirective`](/api/forms/AbstractFormGroupDirective).

@paramdir`AbstractFormGroupDirective`

: The form group directive from which to get the [`FormGroup`](/api/forms/FormGroup).

@returns`FormGroup<any>`

Update the model for a particular control with a new value.

@paramdir`NgControl`

: The control directive to update.

@paramvalue`any`

: The new value for the control.

@returns`void`

## [Description](#description)

An interface implemented by [`FormGroupDirective`](/api/forms/FormGroupDirective) and [`NgForm`](/api/forms/NgForm) directives.

Only used by the [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule) and [`FormsModule`](/api/forms/FormsModule).

Jump to details

----
url: https://angular.dev/api/forms/signals/standardSchemaError
----

```
function standardSchemaError(
  issue: StandardSchemaV1.Issue,
  options: WithFieldTree<ValidationErrorOptions>,
): StandardSchemaValidationError;
function standardSchemaError(
  issue: StandardSchemaV1.Issue,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<StandardSchemaValidationError>;
```

Create a standard schema issue error associated with the target field

@paramissue`StandardSchemaV1.Issue`

The standard schema issue

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`StandardSchemaValidationError`

Create a standard schema issue error

@paramissue`StandardSchemaV1.Issue`

The standard schema issue

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<StandardSchemaValidationError>`

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDropList
----

```
    
      class CdkDropList<T = any> implements OnDestroy {}
    
    
```

Other draggable containers that this container is connected to and into which the container's items can be transferred. Can either be references to other drop containers, or their unique IDs.

Arbitrary data to attach to this container.

Direction in which the list is oriented.

Unique ID for the drop zone. Can be used as a reference in the `connectedTo` of another [`CdkDropList`](/api/cdk/drag-drop/CdkDropList).

Locks the position of the draggable elements inside the container along the specified axis.

Whether starting a dragging sequence from this container is disabled.

Whether sorting within this drop list is disabled.

Function that is used to determine whether an item is allowed to be moved into a drop container.

Functions that is used to determine whether an item can be sorted into a particular index.

Whether to auto-scroll the view when the user moves their pointer close to the edges.

Number of pixels to scroll for each frame when auto-scrolling an element.

Selector that will be used to resolve an alternate element container for the drop list. Passing an alternate container is useful for the cases where one might not have control over the parent node of the draggable items within the list (e.g. due to content projection). This allows for usages like:

```
<div cdkDropList cdkDropListElementContainer=".inner">
  <div class="inner">
    <div cdkDrag></div>
  </div>
</div>
```

By default when an item leaves its initial container, its placeholder will be transferred to the new container. If that's not desirable for your use case, you can enable this option which will clone the placeholder and leave it inside the original container. If the item is returned to the initial container, the anchor element will be removed automatically.

The cloned placeholder can be styled by targeting the `cdk-drag-anchor` class.

This option is useful in combination with `cdkDropListSortingDisabled` to implement copying behavior in a drop list.

Emits when the user drops an item inside the container.

Emits when the user has moved a new drag item into this container.

Emits when the user removes an item from the container by dragging it into another container.

Emits as the user is swapping items while actively dragging.

Registers an items with the drop list.

@paramitem`CdkDrag<any>`

@returns`void`

Removes an item from the drop list.

@paramitem`CdkDrag<any>`

@returns`void`

Gets the registered items in the list, sorted by their position in the DOM.

@returns`CdkDrag<any>[]`

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/TemplateRef
----

```
    
      class TemplateRef<C> {}
    
    
```

@param\_declarationLView`LView<unknown>`

@param\_declarationTContainer`TContainerNode`

@paramelementRef`ElementRef<any>`

@returns`TemplateRef<C>`

The anchor element in the parent view for this embedded view.

The data-binding and [injection contexts](guide/di/dependency-injection-context) of embedded views created from this [`TemplateRef`](/api/core/TemplateRef) inherit from the contexts of this location.

Typically new embedded views are attached to the view container of this location, but in advanced use-cases, the view can be attached to a different container while keeping the data-binding and injection context from the original location.

Instantiates an unattached embedded view based on this template.

@paramcontext`C`

The data-binding context of the embedded view, as declared in the `<ng-template>` usage.

@paraminjector`Injector | undefined`

Injector to be used within the embedded view.

@returns`EmbeddedViewRef<C>`

The new embedded view object.

## [Description](#description)

Represents an embedded template that can be used to instantiate embedded views. To instantiate embedded views based on a template, use the [`ViewContainerRef`](/api/core/ViewContainerRef) method `createEmbeddedView()`.

Access a [`TemplateRef`](/api/core/TemplateRef) instance by placing a directive on an `<ng-template>` element (or directive prefixed with `*`). The [`TemplateRef`](/api/core/TemplateRef) for the embedded view is injected into the constructor of the directive, using the [`TemplateRef`](/api/core/TemplateRef) token.

You can also use a [`Query`](/api/core/Query) to find a [`TemplateRef`](/api/core/TemplateRef) associated with a component or a directive.

Jump to details

----
url: https://angular.dev/api/forms/signals/transformedValue
----

```
function transformedValue<TValue, TRaw>(
  value: ModelSignal<TValue>,
  options: TransformedValueOptions<TValue, TRaw>,
): TransformedValueSignal<TRaw>;
```

@paramvalue`ModelSignal<TValue>`

The model signal to synchronize with.

@paramoptions`TransformedValueOptions<TValue, TRaw>`

Configuration including `parse` and `format` functions.

@returns`TransformedValueSignal<TRaw>`

A [`TransformedValueSignal`](/api/forms/signals/TransformedValueSignal) representing the raw value with parse error tracking.

## [Description](#description)

Creates a writable signal representing a "raw" UI value that is transformed to/from a model value via `parse` and `format` functions.

This utility simplifies the creation of custom form controls that parse a user-facing value representation into an underlying model value. For example, a numeric input that displays and accepts string values but stores a number.

Parse errors are exposed via the returned signal’s `parseErrors()` property. When [`transformedValue`](/api/forms/signals/transformedValue) is used within a Signal Forms field context, parse errors are also reported to the nearest field automatically. When no field context is present, no automatic reporting occurs and `parseErrors` can be consumed directly.

Note: `parse` may return both a `value` and an `error`. Returning `value` updates the model; omitting it leaves the model unchanged.

## [Usage Notes](#usage-notes)

```
@Component({
  selector: 'number-input',
  template: `<input [value]="rawValue()" (input)="rawValue.set($event.target.value)" />`,
})
export class NumberInput implements FormValueControl<number | null> {
  readonly value = model.required<number | null>();

  protected readonly rawValue = transformedValue(this.value, {
    parse: (val) => {
      const parsed = val ? Number(val) : null;

      return Number.isNaN(parsed)
        ? {error: {kind: 'parse', message: `${val} is not a number`}}
        : {value: parsed};
    },
    format: (val) => val?.toString() ?? '',
  });
}
```

Jump to details

----
url: https://angular.dev/api/platform-browser/withEventReplay
----

```
function withEventReplay(): HydrationFeature<HydrationFeatureKind.EventReplay>;
```

@returns`HydrationFeature<HydrationFeatureKind.EventReplay>`

## [Usage Notes](#usage-notes)

Basic example of how you can enable event replay in your application when [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) function is used:

```
bootstrapApplication(App, {
  providers: [provideClientHydration(withEventReplay())]
});
```

Jump to details

----
url: https://angular.dev/api/core/testing/TestComponentOptions
----

```
    
      interface TestComponentOptions {}
    
    
```

Bindings to apply to the test component.

Whether to infer the tag name of the test component from its selector. Otherwise `div` will be used as its tag name.

Jump to details

----
url: https://angular.dev/api/platform-browser/Title
----

```
    
      class Title {}
    
    
```

@param\_doc`any`

@returns`Title`

Get the title of the current HTML document.

@returns`string`

Set the title of the current HTML document.

@paramnewTitle`string`

@returns`void`

## [Description](#description)

A service that can be used to get and set the title of a current HTML document.

Since an Angular application can't be bootstrapped on the entire HTML document (`<html>` tag) it is not possible to bind to the `text` property of the `HTMLTitleElement` elements (representing the `<title>` tag). Instead, this service can be used to set and get the current title value.

Jump to details

----
url: https://angular.dev/api/router/CanDeactivateFn
----

```
    
      type CanDeactivateFn<T> = (  component: T,  currentRoute: ActivatedRouteSnapshot,  currentState: RouterStateSnapshot,  nextState: RouterStateSnapshot,) => MaybeAsync<GuardResult>
    
    
```

## [Description](#description)

The signature of a function used as a `canDeactivate` guard on a [`Route`](/api/router/Route).

If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), the current navigation is cancelled and a new navigation begins to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements and uses a [`CanDeactivateFn`](/api/router/CanDeactivateFn) that checks whether the user component has unsaved changes before navigating away from the route.

```
@Component({
  template: '',
})
export class UserComponent {
  hasUnsavedChanges = true;
}

bootstrapApplication(App, {
  providers: [
    provideRouter([
      {
        path: 'user/:id',
        component: UserComponent,
        canDeactivate: [(component: UserComponent) => !component.hasUnsavedChanges],
      },
    ]),
  ],
});
```

Jump to details

----
url: https://angular.dev/api/forms/signals/FormSubmitOptions
----

```
    
      interface FormSubmitOptions<TRootModel, TSubmittedModel> {}
    
    
```

Function to run when submitting the form data (when form is valid).

Function to run when attempting to submit the form data but validation is failing.

Whether to ignore any of the validators when submitting:

* 'pending': Will submit if there are no invalid validators, pending validators do not block submission (default)
* 'none': Will not submit unless all validators are passing, pending validators block submission
* 'all': Will always submit regardless of invalid or pending validators

Jump to details

----
url: https://angular.dev/best-practices/performance/image-optimization
----

   ```
   import {NgOptimizedImage} from '@angular/common';
   ```

   and include it into the `imports` array of a standalone component or an NgModule:

   ```
   imports: [
     NgOptimizedImage,
     // ...
   ],
   ```

2. ### [(Optional) Set up a Loader](#optional-set-up-a-loader)

   An image loader is not **required** in order to use NgOptimizedImage, but using one with an image CDN enables powerful performance features, including automatic `srcset`s for your images.

   A brief guide for setting up a loader can be found in the [Configuring an Image Loader](#configuring-an-image-loader-for-ngoptimizedimage) section at the end of this page.

3. ### [Enable the directive](#enable-the-directive)

   To activate the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive, replace your image's `src` attribute with [`ngSrc`](/api/common/NgOptimizedImage).

   ```
   <img ngSrc="cat.jpg" />
   ```

   If you're using a [built-in third-party loader](#built-in-loaders), make sure to omit the base URL path from `src`, as that will be prepended automatically by the loader.

4. ### [Mark images as `priority`](#mark-images-as-priority)

   Always mark the [LCP image](https://web.dev/lcp/#what-elements-are-considered) on your page as `priority` to prioritize its loading.

   ```
   <img ngSrc="cat.jpg" width="400" height="200" priority />
   ```

   ```
   <img ngSrc="cat.jpg" width="400" height="200" />
   ```

   For **responsive images** (images which you've styled to grow and shrink relative to the viewport), the `width` and `height` attributes should be the intrinsic size of the image file. For responsive images it's also important to [set a value for `sizes`.](#responsive-images)

   For **fixed size images**, the `width` and `height` attributes should reflect the desired rendered size of the image. The aspect ratio of these attributes should always match the intrinsic aspect ratio of the image.

   **NOTE:** If you don't know the size of your images, consider using "fill mode" to inherit the size of the parent container, as described below.

## [Using `fill` mode](#using-fill-mode)

In cases where you want to have an image fill a containing element, you can use the `fill` attribute. This is often useful when you want to achieve a "background image" behavior. It can also be helpful when you don't know the exact width and height of your image, but you do have a parent container with a known size that you'd like to fit your image into (see "object-fit" below).

When you add the `fill` attribute to your image, you do not need and should not include a `width` and `height`, as in this example:

```
<img ngSrc="cat.jpg" fill />
```

```
<img ngSrc="cat.jpg" width="400" height="200" placeholder />
```

Adding this attribute automatically requests a second, smaller version of the image using your specified image loader. This small image will be applied as a `background-image` style with a CSS blur while your image loads. If no image loader is provided, no placeholder image can be generated and an error will be thrown.

The default size for generated placeholders is 30px wide. You can change this size by specifying a pixel value in the [`IMAGE_CONFIG`](/api/common/IMAGE_CONFIG) provider, as seen below:

```
providers: [
  {
    provide: IMAGE_CONFIG,
    useValue: {
      placeholderResolution: 40
    }
  },
],
```

If you want sharp edges around your blurred placeholder, you can wrap your image in a containing `<div>` with the `overflow: hidden` style. As long as the `<div>` is the same size as the image (such as by using the `width: fit-content` style), the "fuzzy edges" of the placeholder will be hidden.

### [Data URL placeholders](#data-url-placeholders)

You can also specify a placeholder using a base64 [data URL](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) without an image loader. The data url format is `data:image/[imagetype];[data]`, where `[imagetype]` is the image format, just as `png`, and `[data]` is a base64 encoding of the image. That encoding can be done using the command line or in JavaScript. For specific commands, see [the MDN documentation](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URLs#encoding_data_into_base64_format). An example of a data URL placeholder with truncated data is shown below:

```
<img ngSrc="cat.jpg" width="400" height="200" placeholder="data:image/png;base64,iVBORw0K..." />
```

However, large data URLs increase the size of your Angular bundles and slow down page load. If you cannot use an image loader, the Angular team recommends keeping base64 placeholder images smaller than 4KB and using them exclusively on critical images. In addition to decreasing placeholder dimensions, consider changing image formats or parameters used when saving images. At very low resolutions, these parameters can have a large effect on file size.

### [Non-blurred placeholders](#non-blurred-placeholders)

By default, NgOptimizedImage applies a CSS blur effect to image placeholders. To render a placeholder without blur, provide a `placeholderConfig` argument with an object that includes the `blur` property, set to false. For example:

```
<img ngSrc="cat.jpg" width="400" height="200" placeholder [placeholderConfig]="{blur: false}" />
```

```
<link rel="preconnect" href="https://my.cdn.origin" />
```

To disable preconnect warnings, inject the [`PRECONNECT_CHECK_BLOCKLIST`](/api/common/PRECONNECT_CHECK_BLOCKLIST) token:

```
providers: [
{provide: PRECONNECT_CHECK_BLOCKLIST, useValue: 'https://your-domain.com'}
],
```

See more information on automatic preconnect generation [here](#why-is-a-preconnect-element-not-being-generated-for-my-image-domain).

### [Request images at the correct size with automatic `srcset`](#request-images-at-the-correct-size-with-automatic-srcset)

Defining a [`srcset` attribute](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/srcset) ensures that the browser requests an image at the right size for your user's viewport, so it doesn't waste time downloading an image that's too large. [`NgOptimizedImage`](/api/common/NgOptimizedImage) generates an appropriate `srcset` for the image, based on the presence and value of the [`sizes` attribute](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/sizes) on the image tag.

#### [Fixed-size images](#fixed-size-images)

If your image should be "fixed" in size (i.e. the same size across devices, except for [pixel density](https://web.dev/codelab-density-descriptors/)), there is no need to set a `sizes` attribute. A `srcset` can be generated automatically from the image's width and height attributes with no further input required.

Example srcset generated:

```
<img ... srcset="image-400w.jpg 1x, image-800w.jpg 2x" />
```

```
providers: [
  {
    provide: IMAGE_CONFIG,
    useValue: {
      breakpoints: [16, 48, 96, 128, 384, 640, 750, 828, 1080, 1200, 1920]
    }
  },
],
```

If you would like to manually define a `srcset` attribute, you can provide your own using the `ngSrcset` attribute:

```
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" />
```

If the `ngSrcset` attribute is present, [`NgOptimizedImage`](/api/common/NgOptimizedImage) generates and sets the `srcset` based on the sizes included. Do not include image file names in `ngSrcset` - the directive infers this information from [`ngSrc`](/api/common/NgOptimizedImage). The directive supports both width descriptors (e.g. `100w`) and density descriptors (e.g. `1x`).

```
<img ngSrc="hero.jpg" ngSrcset="100w, 200w, 300w" sizes="50vw" />
```

### [Disabling automatic srcset generation](#disabling-automatic-srcset-generation)

To disable srcset generation for a single image, you can add the `disableOptimizedSrcset` attribute on the image:

```
<img ngSrc="about.jpg" disableOptimizedSrcset />
```

### [Disabling image lazy loading](#disabling-image-lazy-loading)

By default, [`NgOptimizedImage`](/api/common/NgOptimizedImage) sets `loading=lazy` for all images that are not marked `priority`. You can disable this behavior for non-priority images by setting the `loading` attribute. This attribute accepts values: `eager`, `auto`, and `lazy`. [See the documentation for the standard image `loading` attribute for details](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/loading#value).

```
<img ngSrc="cat.jpg" width="400" height="200" loading="eager" />
```

### [Controlling image decoding](#controlling-image-decoding)

By default, [`NgOptimizedImage`](/api/common/NgOptimizedImage) sets `decoding="auto"` for all images. This allows the browser to decide the optimal time to decode an image after it has been fetched. When an image is marked as `priority`, Angular automatically sets `decoding="sync"` to ensure the image is decoded and painted as early as possible helping improve **Largest Contentful Paint (LCP)** performance.

You can still override this behavior by explicitly setting the `decoding` attribute.\
[See the documentation for the standard image `decoding` attribute for details](https://developer.mozilla.org/docs/Web/HTML/Element/img#decoding).

```
<!-- Default: decoding is 'auto' -->
<img ngSrc="gallery/landscape.jpg" width="1200" height="800" />

<!-- Decode the image asynchronously to avoid blocking the main thread.-->
<img ngSrc="gallery/preview.jpg" width="600" height="400" decoding="async" />

<!-- Priority images automatically use decoding="sync" -->
<img ngSrc="awesome.jpg" width="500" height="625" priority />

<!-- Decode immediately (can block) when you need the pixels right away -->
<img ngSrc="hero.jpg" width="1600" height="900" decoding="sync" />
```

**Allowed values**

* `auto` (default): lets the browser choose the optimal strategy.
* `async`: decodes the image asynchronously, avoiding main‑thread blocking where possible.
* `sync`: decodes the image immediately; can block rendering but ensures pixels are ready as soon as the image is available.

### [Advanced 'sizes' values](#advanced-sizes-values)

You may want to have images displayed at varying widths on differently-sized screens. A common example of this pattern is a grid- or column-based layout that renders a single column on mobile devices, and two columns on larger devices. You can capture this behavior in the `sizes` attribute, using a "media query" syntax, such as the following:

```
<img ngSrc="cat.jpg" width="400" height="200" sizes="(max-width: 768px) 100vw, 50vw" />
```

The `sizes` attribute in the above example says "I expect this image to be 100 percent of the screen width on devices under 768px wide. Otherwise, I expect it to be 50 percent of the screen width.

For additional information about the `sizes` attribute, see [web.dev](https://web.dev/learn/design/responsive-images/#sizes) or [mdn](https://developer.mozilla.org/docs/Web/API/HTMLImageElement/sizes).

## [Configuring an image loader for `NgOptimizedImage`](#configuring-an-image-loader-for-ngoptimizedimage)

A "loader" is a function that generates an [image transformation URL](https://web.dev/image-cdns/#how-image-cdns-use-urls-to-indicate-optimization-options) for a given image file. When appropriate, [`NgOptimizedImage`](/api/common/NgOptimizedImage) sets the size, format, and image quality transformations for an image.

[`NgOptimizedImage`](/api/common/NgOptimizedImage) provides both a generic loader that applies no transformations, as well as loaders for various third-party image services. It also supports writing your own custom loader.

| Loader type                            | Behavior                                                                                                                                                                                                                       |
| :------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Generic loader                         | The URL returned by the generic loader will always match the value of `src`. In other words, this loader applies no transformations. Sites that use Angular to serve images are the primary intended use case for this loader. |
| Loaders for third-party image services | The URL returned by the loaders for third-party image services will follow API conventions used by that particular image service.                                                                                              |
| Custom loaders                         | A custom loader's behavior is defined by its developer. You should use a custom loader if your image service isn't supported by the loaders that come preconfigured with [`NgOptimizedImage`](/api/common/NgOptimizedImage).   |

Based on the image services commonly used with Angular applications, [`NgOptimizedImage`](/api/common/NgOptimizedImage) provides loaders preconfigured to work with the following image services:

| Image Service             | Angular API                                                      | Documentation                                                               |
| :------------------------ | :--------------------------------------------------------------- | :-------------------------------------------------------------------------- |
| Cloudflare Image Resizing | [`provideCloudflareLoader`](/api/common/provideCloudflareLoader) | [Documentation](https://developers.cloudflare.com/images/image-resizing/)   |
| Cloudinary                | [`provideCloudinaryLoader`](/api/common/provideCloudinaryLoader) | [Documentation](https://cloudinary.com/documentation/resizing_and_cropping) |
| ImageKit                  | [`provideImageKitLoader`](/api/common/provideImageKitLoader)     | [Documentation](https://docs.imagekit.io/)                                  |
| Imgix                     | [`provideImgixLoader`](/api/common/provideImgixLoader)           | [Documentation](https://docs.imgix.com/)                                    |
| Netlify                   | [`provideNetlifyLoader`](/api/common/provideNetlifyLoader)       | [Documentation](https://docs.netlify.com/image-cdn/overview/)               |

To use the **generic loader** no additional code changes are necessary. This is the default behavior.

### [Built-in Loaders](#built-in-loaders)

To use an existing loader for a **third-party image service**, add the provider factory for your chosen service to the `providers` array. In the example below, the Imgix loader is used:

```
providers: [
  provideImgixLoader('https://my.base.url/'),
],
```

```
providers: [
  {
    provide: IMAGE_LOADER,
    useValue: (config: ImageLoaderConfig) => {
      return `https://example.com/images?src=${config.src}&width=${config.width}&height=${config.height}`;
    },
  },
],
```

```
<img
  ngSrc="my-image.jpg"
  width="400"
  height="300"
  [loaderParams]="{transform: 'e_grayscale,r_10'}"
/>
```

#### [Object format](#object-format)

Provide transformations as an object with key-value pairs.

```
<img
  ngSrc="my-image.jpg"
  width="400"
  height="300"
  [loaderParams]="{transform: {e: 'grayscale', r: 10}}"
/>
```

**NOTE:** The `transform` property is not supported by the Netlify loader, as Netlify's image CDN does not provide custom transformation parameters.

### [Example custom loader](#example-custom-loader)

The following shows an example of a custom loader function. This example function concatenates `src`, `width`, and `height`, and uses `loaderParams` to control a custom CDN feature for rounded corners:

```
const myCustomLoader = (config: ImageLoaderConfig) => {
  let url = `https://example.com/images/${config.src}?`;
  let queryParams = [];
  if (config.width) {
    queryParams.push(`w=${config.width}`);
  }
  if (config.height) {
    queryParams.push(`h=${config.height}`);
  }
  if (config.loaderParams?.roundedCorners) {
    queryParams.push('mask=corners&corner-radius=5');
  }
  return url + queryParams.join('&');
};
```

Note that in the above example, we've invented the 'roundedCorners' property name to control a feature of our custom loader. We could then use this feature when creating an image, as follows:

```
<img ngSrc="profile.jpg" width="300" height="300" [loaderParams]="{roundedCorners: true}" />
```

```
providers: [
  provideImgixLoader('https://my.base.url/'),
],
```

----
url: https://angular.dev/guide/components/programmatic-rendering
----

In addition to using a component directly in a template, you can also dynamically render components programmatically. This is helpful for situations when a component is unknown initially (thus can not be referenced in a template directly) and it depends on some conditions.

There are two main ways to render a component programmatically: in a template using [`NgComponentOutlet`](/api/common/NgComponentOutlet), or in your TypeScript code using [`ViewContainerRef`](/api/core/ViewContainerRef).

**HELPFUL:** for lazy-loading use-cases (for example if you want to delay loading of a heavy component), consider using the built-in [`@defer` feature](/guide/templates/defer) instead. The `@defer` feature allows the code of any components, directives, and pipes inside the `@defer` block to be extracted into separate JavaScript chunks automatically and loaded only when necessary, based on the configured triggers.

## [Using NgComponentOutlet](#using-ngcomponentoutlet)

[`NgComponentOutlet`](/api/common/NgComponentOutlet) is a structural directive that dynamically renders a given component in a template.

```
import {NgComponentOutlet} from '@angular/common';

@Component({/*...*/})
export class AdminBio {
  /* ... */
}

@Component({/*...*/})
export class StandardBio {
  /* ... */
}

@Component({
  imports: [NgComponentOutlet],
  template: `
    <p>Profile for {{ user().name }}</p>
    <ng-container *ngComponentOutlet="bioComponent()" />
  `,
})
export class CustomDialog {
  user = input.required<User>();

  bioComponent = computed(() => {
    return this.user().isAdmin ? AdminBio : StandardBio;
  });
}
```

### [Passing inputs to dynamically rendered components](#passing-inputs-to-dynamically-rendered-components)

You can pass inputs to the dynamically rendered component using the `ngComponentOutletInputs` property. This property accepts an object where keys are input names and values are the input values.

```
@Component({
  selector: 'user-greeting',
  template: `
    <div>
      <p>User: {{ username() }}</p>
      <p>Role: {{ role() }}</p>
    </div>
  `,
})
export class UserGreeting {
  username = input.required<string>();
  role = input('guest');
}

@Component({
  selector: 'profile-view',
  imports: [NgComponentOutlet],
  template: `<ng-container *ngComponentOutlet="greetingComponent; inputs: greetingInputs()" />`,
})
export class ProfileView {
  greetingComponent = UserGreeting;
  greetingInputs = signal({username: 'ngAwesome', role: 'admin'});
}
```

The inputs are updated whenever the `greetingInputs` signal changes, keeping the dynamic component in sync with the parent's state.

### [Providing content projection](#providing-content-projection)

Use `ngComponentOutletContent` to pass projected content to the dynamically rendered component. This is useful when the dynamic component uses `<ng-content>` to display content.

```
@Component({
  selector: 'card-wrapper',
  template: `
    <div class="card">
      <ng-content />
    </div>
  `,
})
export class CardWrapper {}

@Component({
  imports: [NgComponentOutlet],
  template: `
    <ng-container *ngComponentOutlet="cardComponent; content: cardContent()" />

    <ng-template #contentTemplate>
      <h3>Dynamic Content</h3>
      <p>This content is projected into the card.</p>
    </ng-template>
  `,
})
export class DynamicCard {
  private vcr = inject(ViewContainerRef);
  cardComponent = CardWrapper;

  private contentTemplate = viewChild<TemplateRef<unknown>>('contentTemplate');

  cardContent = computed(() => {
    const template = this.contentTemplate();
    if (!template) return [];
    // Returns an array of projection slots. Each element represents one <ng-content> slot.
    // CardWrapper has one <ng-content>, so we return an array with one element.
    return [this.vcr.createEmbeddedView(template).rootNodes];
  });
}
```

**NOTE:** Hydration does not support projecting DOM nodes created with native DOM APIs. This causes an [NG0503 error](/errors/NG0503). Use Angular APIs to create projected content or add `ngSkipHydration` to the component.

### [Providing injectors](#providing-injectors)

You can provide a custom injector to the dynamically created component using `ngComponentOutletInjector`. This is useful for providing component-specific services or configuration.

```
export const THEME_DATA = new InjectionToken<string>('THEME_DATA', {
  factory: () => 'light',
});

@Component({
  selector: 'themed-panel',
  template: `<div [class]="theme">...</div>`,
})
export class ThemedPanel {
  theme = inject(THEME_DATA);
}

@Component({
  selector: 'dynamic-panel',
  imports: [NgComponentOutlet],
  template: `<ng-container *ngComponentOutlet="panelComponent; injector: customInjector" />`,
})
export class DynamicPanel {
  panelComponent = ThemedPanel;

  customInjector = Injector.create({
    providers: [{provide: THEME_DATA, useValue: 'dark'}],
  });
}
```

### [Accessing the component instance](#accessing-the-component-instance)

You can access the dynamically created component's instance using the directive's `exportAs` feature:

```
@Component({
  selector: 'counter',
  template: `<p>Count: {{ count() }}</p>`,
})
export class Counter {
  count = signal(0);
  increment() {
    this.count.update((c) => c + 1);
  }
}

@Component({
  imports: [NgComponentOutlet],
  template: `
    <ng-container [ngComponentOutlet]="counterComponent" #outlet="ngComponentOutlet" />

    <button (click)="outlet.componentInstance?.increment()">Increment</button>
  `,
})
export class CounterHost {
  counterComponent = Counter;
}
```

**NOTE:** The `componentInstance` property is `null` before the component is rendered.

See the [NgComponentOutlet API reference](api/common/NgComponentOutlet) for more information on the directive's capabilities.

## [Using ViewContainerRef](#using-viewcontainerref)

A **view container** is a node in Angular's component tree that can contain content. Any component or directive can inject [`ViewContainerRef`](/api/core/ViewContainerRef) to get a reference to a view container corresponding to that component or directive's location in the DOM.

You can use the [`createComponent`](/api/core/createComponent)method on [`ViewContainerRef`](/api/core/ViewContainerRef) to dynamically create and render a component. When you create a new component with a [`ViewContainerRef`](/api/core/ViewContainerRef), Angular appends it into the DOM as the next sibling of the component or directive that injected the [`ViewContainerRef`](/api/core/ViewContainerRef).

```
@Component({
  selector: 'leaf-content',
  template: `This is the leaf content`,
})
export class LeafContent {}

@Component({
  selector: 'outer-container',
  template: `
    <p>This is the start of the outer container</p>
    <inner-item />
    <p>This is the end of the outer container</p>
  `,
})
export class OuterContainer {}

@Component({
  selector: 'inner-item',
  template: `<button (click)="loadContent()">Load content</button>`,
})
export class InnerItem {
  private viewContainer = inject(ViewContainerRef);

  loadContent() {
    this.viewContainer.createComponent(LeafContent);
  }
}
```

In the example above, clicking the "Load content" button results in the following DOM structure

```
<outer-container>
  <p>This is the start of the outer container</p>
  <inner-item>
    <button>Load content</button>
  </inner-item>
  <leaf-content>This is the leaf content</leaf-content>
  <p>This is the end of the outer container</p>
</outer-container>
```

## [Lazy-loading components](#lazy-loading-components)

**HELPFUL:** if you want to lazy-load some components, you may consider using the built-in [`@defer` feature](/guide/templates/defer) instead.

If your use-case is not covered by the `@defer` feature, you can use either [`NgComponentOutlet`](/api/common/NgComponentOutlet) or [`ViewContainerRef`](/api/core/ViewContainerRef) with a standard JavaScript [dynamic import](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import).

```
@Component({
  ...,
  template: `
    <section>
      <h2>Basic settings</h2>
      <basic-settings />
    </section>
    <section>
      <h2>Advanced settings</h2>
      @if(!advancedSettings) {
        <button (click)="loadAdvanced()">
          Load advanced settings
        </button>
      }
      <ng-container *ngComponentOutlet="advancedSettings" />
    </section>
  `
})
export class AdminSettings {
  advancedSettings: {new(): AdvancedSettings} | undefined;

  async loadAdvanced() {
    const { AdvancedSettings } = await import('path/to/advanced_settings.js');
    this.advancedSettings = AdvancedSettings;
  }
}
```

The example above loads and displays the `AdvancedSettings` upon receiving a button click.

## [Binding inputs, outputs and setting host directives at creation](#binding-inputs-outputs-and-setting-host-directives-at-creation)

When dynamically creating components, manually setting inputs and subscribing to outputs can be error-prone. You often need to write extra code just to wire up bindings after the component is instantiated.

To simplify this, both [`createComponent`](/api/core/createComponent) and [`ViewContainerRef.createComponent`](/api/core/ViewContainerRef#createComponent) support passing a `bindings` array with helpers like [`inputBinding()`](/api/core/inputBinding), [`outputBinding()`](/api/core/outputBinding), and [`twoWayBinding()`](/api/core/twoWayBinding) to configure inputs and outputs up front. You can also specify a `directives` array to apply any host directives. This enables creating components programmatically with template-like bindings in a single, declarative call.

### [Host view using `ViewContainerRef.createComponent`](#host-view-using-viewcontainerrefcreatecomponent)

[`ViewContainerRef.createComponent`](/api/core/ViewContainerRef#createComponent) creates a component and automatically inserts its host view and host element into the container’s view hierarchy at the container’s location. Use this when the dynamic component should become part of the container’s logical and visual structure (for example, adding list items or inline UI).

By contrast, the standalone [`createComponent`](/api/core/createComponent) API does not attach the new component to any existing view or DOM location — it returns a [`ComponentRef`](/api/core/ComponentRef) and gives you explicit control over where to place the component’s host element.

```
import {Component, input, model, output} from '@angular/core';

@Component({
  selector: 'app-warning',
  template: `
    @if (isExpanded()) {
      <section>
        <p>Warning: Action needed!</p>
        <button (click)="close.emit(true)">Close</button>
      </section>
    }
  `,
})
export class AppWarning {
  readonly canClose = input.required<boolean>();
  readonly isExpanded = model<boolean>();
  readonly close = output<boolean>();
}
```

```
import {
  Component,
  ViewContainerRef,
  signal,
  inputBinding,
  outputBinding,
  twoWayBinding,
  inject,
} from '@angular/core';
import {FocusTrap} from '@angular/cdk/a11y';
import {ThemeDirective} from '../theme.directive';

@Component({
  template: `<ng-container #container />`,
})
export class Host {
  private vcr = inject(ViewContainerRef);
  readonly canClose = signal(true);
  readonly isExpanded = signal(true);

  showWarning() {
    const compRef = this.vcr.createComponent(AppWarning, {
      bindings: [
        inputBinding('canClose', this.canClose),
        twoWayBinding('isExpanded', this.isExpanded),
        outputBinding<boolean>('close', (confirmed) => {
          console.log('Closed with result:', confirmed);
        }),
      ],
      directives: [
        FocusTrap,
        {type: ThemeDirective, bindings: [inputBinding('theme', () => 'warning')]},
      ],
    });
  }
}
```

In the example above, the dynamic **AppWarning** is created with its `canClose` input bound to a reactive signal, a two-way binding on its `isExpanded` state, and an output listener for `close`. The `FocusTrap` and `ThemeDirective` are attached to the host element via `directives`.

### [Popup attached to `document.body` with `createComponent` + `hostElement`](#popup-attached-to-documentbody-with-createcomponent--hostelement)

Use this when rendering outside the current view hierarchy (e.g., overlays). The provided `hostElement` becomes the component’s host in the DOM, so Angular doesn’t create a new element matching the selector. Lets you configure **bindings** directly.

```
import {
  ApplicationRef,
  createComponent,
  EnvironmentInjector,
  inject,
  Injectable,
  inputBinding,
  outputBinding,
  Service,
} from '@angular/core';
import {Popup} from './popup';

@Service()
export class PopupService {
  private readonly injector = inject(EnvironmentInjector);
  private readonly appRef = inject(ApplicationRef);

  show(message: string) {
    // Create a host element for the popup
    const host = document.createElement('popup-host');

    // Create the component and bind in one call
    const ref = createComponent(Popup, {
      environmentInjector: this.injector,
      hostElement: host,
      bindings: [
        inputBinding('message', () => message),
        outputBinding('closed', () => {
          document.body.removeChild(host);
          this.appRef.detachView(ref.hostView);
          ref.destroy();
        }),
      ],
    });

    // Registers the component’s view so it participates in change detection cycle.
    this.appRef.attachView(ref.hostView);
    // Inserts the provided host element into the DOM (outside the normal Angular view hierarchy).
    // This is what makes the popup visible on screen, typically used for overlays or modals.
    document.body.appendChild(host);
  }
}
```

## [Handling rendering errors](#handling-rendering-errors)

When dynamically creating components using [`ViewContainerRef.createComponent`](/api/core/ViewContainerRef#createComponent) or the standalone [`createComponent`](/api/core/createComponent) function, you can provide an `onError` callback in the options object to handle errors that occur during the rendering or change detection phases. This is the programmatic equivalent of using an `@error` block in templates.

```
viewContainerRef.createComponent(DynamicComponent, {
  onError: (err: Error, details: ErrorDetails) => {
    console.error('Component rendering failed:', err);
    // Render an alternative UI or log metrics
  },
});
```

**NOTE:** The `onError` callback only catches errors that occur during the rendering or change detection phases. It does not catch errors that occur during component instantiation (for example, in the constructor). Angular throws construction errors synchronously when you call the API.

----
url: https://angular.dev/tutorials/first-app/02-Home
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/01-hello-world)[](/tutorials/first-app/03-HousingLocation)

[](/tutorials/first-app/01-hello-world)[](/tutorials/first-app/03-HousingLocation)

This tutorial lesson demonstrates how to create a new [component](guide/components) for your Angular app.

[](https://www.youtube.com/watch?v=R0nRX8jD2D0\&autoplay=1)

## [What you'll learn](#what-youll-learn)

Your app has a new component: `Home`.

## [Conceptual preview of Angular components](#conceptual-preview-of-angular-components)

Angular apps are built around components, which are Angular's building blocks. Components contain the code, HTML layout, and CSS style information that provide the function and appearance of an element in the app. In Angular, components can contain other components. An app's functions and appearance can be divided and partitioned into components.

In Angular, components have metadata that define its properties. When you create your `Home`, you use these properties:

* `selector`: to describe how Angular refers to the component in templates.
* `imports`: to describe the component's dependencies.
* `template`: to describe the component's HTML markup and layout.
* `styleUrls`: to list the URLs of the CSS files that the component uses in an array.

1. ### [Create the `Home`](#create-the-home)

   In this step, you create a new component for your app.

   In the **Terminal** pane of your IDE:

   1. In your project directory, navigate to the `first-app` directory.

   2. Run this command to create a new `Home`

      ```
      ng generate component home
      ```

   3. Run this command to build and serve your app.

      **NOTE:** This step is only for your local environment!

      ```
      ng serve
      ```

   4. Open a browser and navigate to `http://localhost:4200` to find the application.

   5. Confirm that the app builds without error.

      **HELPFUL:** It should render the same as it did in the previous lesson because even though you added a new component, you haven't included it in any of the app's templates, yet.

   6. Leave `ng serve` running as you complete the next steps.

2. ### [Add the new component to your app's layout](#add-the-new-component-to-your-apps-layout)

   In this step, you add the new component, `Home` to your app's root component, `App`, so that it displays in your app's layout.

   In the **Edit** pane of your IDE:

   1. Open `app.ts` in the editor.

   2. In `app.ts`, import `Home` by adding this line to the file level imports.

      ### Import Home in src/app/app.ts

      ```
      import {Component} from '@angular/core';
      import {Home} from './home/home';

      @Component({
        selector: 'app-root',
        imports: [Home],
        template: `
          <main>
            <header class="brand-name">
              <img class="brand-logo" src="/public/logo.svg" alt="logo" aria-hidden="true" />
            </header>
            <section class="content">
              <app-home />
            </section>
          </main>
        `,
        styleUrls: ['./app.css'],
      })
      export class App {
        title = 'homes';
      }
      ```

   3. In `app.ts`, in [`@Component`](/api/core/Component), update the `imports` array property and add `Home`.

      ### Replace in src/app/app.ts

      ```
      import {Component} from '@angular/core';
      import {Home} from './home/home';

      @Component({
        selector: 'app-root',
        imports: [Home],
        template: `
          <main>
            <header class="brand-name">
              <img class="brand-logo" src="/public/logo.svg" alt="logo" aria-hidden="true" />
            </header>
            <section class="content">
              <app-home />
            </section>
          </main>
        `,
        styleUrls: ['./app.css'],
      })
      export class App {
        title = 'homes';
      }
      ```

   4. In `app.ts`, in [`@Component`](/api/core/Component), update the `template` property to include the following HTML code.

      ### Replace in src/app/app.ts

      ```
      import {Component} from '@angular/core';
      import {Home} from './home/home';

      @Component({
        selector: 'app-root',
        imports: [Home],
        template: `
          <main>
            <header class="brand-name">
              <img class="brand-logo" src="/public/logo.svg" alt="logo" aria-hidden="true" />
            </header>
            <section class="content">
              <app-home />
            </section>
          </main>
        `,
        styleUrls: ['./app.css'],
      })
      export class App {
        title = 'homes';
      }
      ```

   5. Save your changes to `app.ts`.

   6. If `ng serve` is running, the app should update. If `ng serve` is not running, start it again. *Hello world* in your app should change to *home works!* from the `Home`.

   7. Check the running app in the browser and confirm that the app has been updated.

3. ### [Add features to `Home`](#add-features-to-home)

   In this step you add features to `Home`.

   In the previous step, you added the default `Home` to your app's template so its default HTML appeared in the app. In this step, you add a search filter and button that is used in a later lesson. For now, that's all that `Home` has. Note that this step just adds the search elements to the layout without any functionality, yet.

   If you started from a fresh Angular project instead of downloading the starter (ng new): add these globals to `src/styles.css` so the search button and input border are visible:

   ```
   :root {
     --primary-color: #605dc8;
     --secondary-color: #8b89e6;
     --accent-color: #e8e7fa;
     --shadow-color: #e8e8e8;
   }

   button.primary {
     padding: 10px;
     border: solid 1px var(--primary-color);
     background: var(--primary-color);
     color: white;
     border-radius: 8px;
   }
   ```

   In the **Edit** pane of your IDE:

   1. In the `first-app` directory, open `home.ts` in the editor.

   2. In `home.ts`, in [`@Component`](/api/core/Component), update the `template` property with this code.

      ### Replace in src/app/home/home.ts

      ```
      import {Component} from '@angular/core';

      @Component({
        selector: 'app-home',
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {}
      ```

   3. Next, open `home.css` in the editor and update the content with these styles.

      **NOTE:** In the browser, these can go in `src/app/home/home.ts` in the `styles` array.

      ### Replace in src/app/home/home.css

      ```
      .results {
        display: grid;
        column-gap: 14px;
        row-gap: 14px;
        grid-template-columns: repeat(auto-fill, minmax(400px, 400px));
        margin-top: 50px;
        justify-content: space-around;
      }

      input[type="text"] {
        border: solid 1px var(--primary-color);
        padding: 10px;
        border-radius: 8px;
        margin-right: 4px;
        display: inline-block;
        width: 30%;
      }

      button {
        padding: 10px;
        border: solid 1px var(--primary-color);
        background: var(--primary-color);
        color: white;
        border-radius: 8px;
      }

      @media (min-width: 500px) and (max-width: 768px) {
        .results {
            grid-template-columns: repeat(2, 1fr);
        }
        input[type="text"] {
            width: 70%;
        }   
      }

      @media (max-width: 499px) {
        .results {
            grid-template-columns: 1fr;
        }    
      }
      ```

   4. Confirm that the app builds without error. You should find the filter query box and button in your app and they should be styled. Correct any errors before you continue to the next step.

**SUMMARY:** In this lesson, you created a new component for your app and gave it a filter edit control and button.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/guide/components/lifecycle
----

A component's **lifecycle** is the sequence of steps that happen between the component's creation and its destruction. Each step represents a different part of Angular's process for rendering components and checking them for updates over time.

In your components, you can implement **lifecycle hooks** to run code during these steps. Lifecycle hooks that relate to a specific component instance are implemented as methods on your component class. Lifecycle hooks that relate the Angular application as a whole are implemented as functions that accept a callback.

A component's lifecycle is tightly connected to how Angular checks your components for changes over time. For the purposes of understanding this lifecycle, you only need to know that Angular walks your application tree from top to bottom, checking template bindings for changes. The lifecycle hooks described below run while Angular is doing this traversal. This traversal visits each component exactly once, so you should always avoid making further state changes in the middle of the process.

## [Summary](#summary)

|                  |                         |                                                                                                                                                                          |
| ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Phase**        | **Method**              | **Summary**                                                                                                                                                              |
| Creation         | `constructor`           | [Standard JavaScript class constructor ](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes/constructor). Runs when Angular instantiates the component. |
| Change Detection | `ngOnInit`              | Runs once after Angular has initialized all the component's inputs.                                                                                                      |
|                  | `ngOnChanges`           | Runs every time the component's inputs have changed.                                                                                                                     |
|                  | `ngDoCheck`             | Runs every time this component is checked for changes.                                                                                                                   |
|                  | `ngAfterContentInit`    | Runs once after the component's *content* has been initialized.                                                                                                          |
|                  | `ngAfterContentChecked` | Runs every time this component content has been checked for changes.                                                                                                     |
|                  | `ngAfterViewInit`       | Runs once after the component's *view* has been initialized.                                                                                                             |
|                  | `ngAfterViewChecked`    | Runs every time the component's view has been checked for changes.                                                                                                       |
| Rendering        | `afterNextRender`       | Runs once the next time that **all** components have been rendered to the DOM.                                                                                           |
|                  | `afterEveryRender`      | Runs every time **all** components have been rendered to the DOM.                                                                                                        |
| Destruction      | `ngOnDestroy`           | Runs once before the component is destroyed.                                                                                                                             |

### [ngOnInit](#ngoninit)

The `ngOnInit` method runs after Angular has initialized all the components inputs with their initial values. A component's `ngOnInit` runs exactly once.

This step happens *before* the component's own template is initialized. This means that you can update the component's state based on its initial input values.

### [ngOnChanges](#ngonchanges)

The `ngOnChanges` method runs after any component inputs have changed.

This step happens *before* the component's own template is checked. This means that you can update the component's state based on its initial input values.

During initialization, the first `ngOnChanges` runs before `ngOnInit`.

#### [Inspecting changes](#inspecting-changes)

The `ngOnChanges` method accepts one [`SimpleChanges`](/api/core/SimpleChanges) argument. This object is a [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type) mapping each component input name to a [`SimpleChange`](/api/core/SimpleChange) object. Each [`SimpleChange`](/api/core/SimpleChange) contains the input's previous value, its current value, and a flag for whether this is the first time the input has changed.

You can optionally pass the current class or this as the first generic argument for stronger type checking.

```
@Component({/* ... */})
export class UserProfile {
  name = input('');

  ngOnChanges(changes: SimpleChanges<UserProfile>) {
    if (changes.name) {
      console.log(`Previous: ${changes.name.previousValue}`);
      console.log(`Current: ${changes.name.currentValue}`);
      console.log(`Is first ${changes.name.firstChange}`);
    }
  }
}
```

If you provide an `alias` for any input properties, the [`SimpleChanges`](/api/core/SimpleChanges) Record still uses the TypeScript property name as a key, rather than the alias.

### [ngOnDestroy](#ngondestroy)

The `ngOnDestroy` method runs once just before a component is destroyed. Angular destroys a component when it is no longer shown on the page, such as being hidden by `@if` or upon navigating to another page.

#### [DestroyRef](#destroyref)

As an alternative to the `ngOnDestroy` method, you can inject an instance of [`DestroyRef`](/api/core/DestroyRef). You can register a callback to be invoked upon the component's destruction by calling the `onDestroy` method of [`DestroyRef`](/api/core/DestroyRef).

```
@Component({/* ... */})
export class UserProfile {
  constructor() {
    inject(DestroyRef).onDestroy(() => {
      console.log('UserProfile destruction');
    });
  }
}
```

You can pass the [`DestroyRef`](/api/core/DestroyRef) instance to functions or classes outside your component. Use this pattern if you have other code that should run some cleanup behavior when the component is destroyed.

You can also use [`DestroyRef`](/api/core/DestroyRef) to keep setup code close to cleanup code, rather than putting all cleanup code in the `ngOnDestroy` method.

##### [Detecting instance destruction](#detecting-instance-destruction)

[`DestroyRef`](/api/core/DestroyRef) provides a `destroyed` property that allows checking whether a given instance has already been destroyed. This is useful for avoiding operations on destroyed components, especially when dealing with delayed or asynchronous logic.

By checking `destroyRef.destroyed`, you can prevent executing code after the instance has been cleaned up, avoiding potential errors such as `NG0911: View has already been destroyed.`.

### [ngDoCheck](#ngdocheck)

The `ngDoCheck` method runs before every time Angular checks a component's template for changes.

You can use this lifecycle hook to manually check for state changes outside of Angular's normal change detection, manually updating the component's state.

This method runs very frequently and can significantly impact your page's performance. Avoid defining this hook whenever possible, only using it when you have no alternative.

During initialization, the first `ngDoCheck` runs after `ngOnInit`.

### [ngAfterContentInit](#ngaftercontentinit)

The `ngAfterContentInit` method runs once after all the children nested inside the component (its *content*) have been initialized.

You can use this lifecycle hook to read the results of [content queries](guide/components/queries#content-queries). While you can access the initialized state of these queries, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](errors/NG0100)

### [ngAfterContentChecked](#ngaftercontentchecked)

The `ngAfterContentChecked` method runs every time the children nested inside the component (its *content*) have been checked for changes.

This method runs very frequently and can significantly impact your page's performance. Avoid defining this hook whenever possible, only using it when you have no alternative.

While you can access the updated state of [content queries](guide/components/queries#content-queries) here, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](errors/NG0100).

### [ngAfterViewInit](#ngafterviewinit)

The `ngAfterViewInit` method runs once after all the children in the component's template (its *view*) have been initialized.

You can use this lifecycle hook to read the results of [view queries](guide/components/queries#view-queries). While you can access the initialized state of these queries, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](errors/NG0100)

### [ngAfterViewChecked](#ngafterviewchecked)

The `ngAfterViewChecked` method runs every time the children in the component's template (its *view*) have been checked for changes.

This method runs very frequently and can significantly impact your page's performance. Avoid defining this hook whenever possible, only using it when you have no alternative.

While you can access the updated state of [view queries](guide/components/queries#view-queries) here, attempting to change any state in this method results in an [ExpressionChangedAfterItHasBeenCheckedError](errors/NG0100).

### [afterEveryRender and afterNextRender](#aftereveryrender-and-afternextrender)

The [`afterEveryRender`](/api/core/afterEveryRender) and [`afterNextRender`](/api/core/afterNextRender) functions let you register a **render callback** to be invoked after Angular has finished rendering *all components* on the page into the DOM.

These functions are different from the other lifecycle hooks described in this guide. Rather than a class method, they are standalone functions that accept a callback. The execution of render callbacks are not tied to any specific component instance, but instead an application-wide hook.

[`afterEveryRender`](/api/core/afterEveryRender) and [`afterNextRender`](/api/core/afterNextRender) must be called in an [injection context](guide/di/dependency-injection-context), typically a component's constructor.

You can use render callbacks to perform manual DOM operations. See [Using DOM APIs](guide/components/dom-apis) for guidance on working with the DOM in Angular.

Render callbacks do not run during server-side rendering or during build-time pre-rendering.

#### [after\*Render phases](#afterrender-phases)

When using [`afterEveryRender`](/api/core/afterEveryRender) or [`afterNextRender`](/api/core/afterNextRender), you can optionally split the work into phases. The phase gives you control over the sequencing of DOM operations, letting you sequence *write* operations before *read* operations in order to minimize [layout thrashing](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing). In order to communicate across phases, a phase function may return a result value that can be accessed in the next phase.

```
import {Component, ElementRef, afterNextRender} from '@angular/core';

@Component(/* ... */)
export class UserProfile {
  private prevPadding = 0;
  private elementHeight = 0;

  constructor() {
    const elementRef = inject(ElementRef);
    const nativeElement = elementRef.nativeElement;

    afterNextRender({
      // Use the `Write` phase to write to a geometric property.
      write: () => {
        const padding = computePadding();
        const changed = padding !== this.prevPadding;
        if (changed) {
          nativeElement.style.padding = padding;
        }
        return changed; // Communicate whether anything changed to the read phase.
      },

      // Use the `Read` phase to read geometric properties after all writes have occurred.
      read: (didWrite) => {
        if (didWrite) {
          this.elementHeight = nativeElement.getBoundingClientRect().height;
        }
      },
    });
  }
}
```

There are four phases, run in the following order:

| Phase            | Description                                                                                                                                                                                           |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `earlyRead`      | Use this phase to read any layout-affecting DOM properties and styles that are strictly necessary for subsequent calculation. Avoid this phase if possible, preferring the `write` and `read` phases. |
| `write`          | Use this phase to write layout-affecting DOM properties and styles.                                                                                                                                   |
| `mixedReadWrite` | Default phase. Use for any operations need to both read and write layout-affecting properties and styles. Avoid this phase if possible, preferring the explicit `write` and `read` phases.            |
| `read`           | Use this phase to read any layout-affecting DOM properties.                                                                                                                                           |

## [Lifecycle interfaces](#lifecycle-interfaces)

Angular provides a TypeScript interface for each lifecycle method. You can optionally import and `implement` these interfaces to ensure that your implementation does not have any typos or misspellings.

Each interface has the same name as the corresponding method without the `ng` prefix. For example, the interface for `ngOnInit` is [`OnInit`](/api/core/OnInit).

```
@Component({/* ... */})
export class UserProfile implements OnInit {
  ngOnInit() {
    /* ... */
  }
}
```

## [Execution order](#execution-order)

The following diagrams show the execution order of Angular's lifecycle hooks.

### [During initialization](#during-initialization)

### [Subsequent updates](#subsequent-updates)

### [Ordering with directives](#ordering-with-directives)

When you put one or more directives on the same element as a component, either in a template or with the `hostDirectives` property, the framework does not guarantee any ordering of a given lifecycle hook between the component and the directives on a single element. Never depend on an observed ordering, as this may change in later versions of Angular.

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragHandle
----

```
    
      class CdkDragHandle implements AfterViewInit ,OnDestroy {}
    
    
```

Whether starting to drag through this handle is disabled.

@returns`void`

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/globals/enableProfiling
----

```
function enableProfiling(): () => void;
```

@returns`() => void`

a function that can be invoked to stop sending profiling data.

## [Description](#description)

Start listening to the Angular's internal performance-related events and route those to the Chrome DevTools performance panel. This enables Angular-specific data visualization when recording a performance profile directly in the Chrome DevTools.

**NOTE:** Integration is enabled in the development mode only, this operation is noop in the production mode.

Jump to details

----
url: https://angular.dev/api/core/importProvidersFrom
----

```
function importProvidersFrom(
  ...sources: ImportProvidersSource[]
): EnvironmentProviders;
```

@paramsources`ImportProvidersSource[]`

@returns`EnvironmentProviders`

Collected providers from the specified list of types.

## [Description](#description)

Collects providers from all NgModules and standalone components, including transitively imported ones.

Providers extracted via [`importProvidersFrom`](/api/core/importProvidersFrom) are only usable in an application injector or another environment injector (such as a route injector). They should not be used in component providers.

## [Usage Notes](#usage-notes)

The results of the [`importProvidersFrom`](/api/core/importProvidersFrom) call can be used in the `bootstrapApplication` call:

```
await bootstrapApplication(RootComponent, {
  providers: [
    importProvidersFrom(NgModuleOne, NgModuleTwo)
  ]
});
```

You can also use the [`importProvidersFrom`](/api/core/importProvidersFrom) results in the `providers` field of a route, when a standalone component is used:

```
export const ROUTES: Route[] = [
  {
    path: 'foo',
    providers: [
      importProvidersFrom(NgModuleOne, NgModuleTwo)
    ],
    component: YourStandaloneComponent
  }
];
```

Jump to details

----
url: https://angular.dev/api/forms/signals/ReadonlyFieldState
----

## [API](/api/forms/signals/ReadonlyFieldState#api)

```
    
      interface ReadonlyFieldState<TValue, TKey extends string | number = string | number> {}
    
    
```

The [`FieldTree`](/api/forms/signals/FieldTree) associated with this field state.

A writable signal containing the value for this field.

Updating this signal will update the data model that the field is bound to.

While updates from the UI control are eventually reflected here, they may be delayed if debounced.

A signal containing the value of the control to which this field is bound.

This differs from [`value`](/api/forms/signals/ReadonlyFieldState#value) in that it's not subject to debouncing, and thus is used to buffer debounced updates from the control to the field. This will also not take into account the [`controlValue`](/api/forms/signals/ReadonlyFieldState#controlValue) of children.

```
@if (!field.hidden()) {
  ...
}
```

A signal containing the [`errors`](/api/forms/signals/ReadonlyFieldState#errors) of the field itself, excluding its descendants.

A signal containing the [`errors`](/api/forms/signals/ReadonlyFieldState#errors) of the field and its descendants.

----
url: https://angular.dev/ecosystem/web-workers
----

[Web workers](https://developer.mozilla.org/docs/Web/API/Web_Workers_API) let you run CPU-intensive computations in a background thread, freeing the main thread to update the user interface. Application's performing a lot of computations, like generating Computer-Aided Design (CAD) drawings or doing heavy geometric calculations, can use web workers to increase performance.

**HELPFUL:** The Angular CLI does not support running itself in a web worker.

## [Adding a web worker](#adding-a-web-worker)

To add a web worker to an existing project, use the Angular CLI `ng generate` command.

```
ng generate web-worker <location>
```

You can add a web worker anywhere in your application. For example, to add a web worker to the root component, `src/app/app.component.ts`, run the following command.

```
ng generate web-worker app
```

The command performs the following actions.

1. Configures your project to use web workers, if it isn't already.

2. Adds the following scaffold code to `src/app/app.worker.ts` to receive messages.

   ### src/app/app.worker.ts

   ```
   addEventListener('message', ({data}) => {
     const response = `worker response to ${data}`;
     postMessage(response);
   });
   ```

3. Adds the following scaffold code to `src/app/app.component.ts` to use the worker.

   ### src/app/app.component.ts

   ```
   if (typeof Worker !== 'undefined') {
     // Create a new
     const worker = new Worker(new URL('./app.worker', import.meta.url));
     worker.onmessage = ({data}) => {
       console.log(`page got message: ${data}`);
     };
     worker.postMessage('hello');
   } else {
     // Web workers are not supported in this environment.
     // You should add a fallback so that your program still executes correctly.
   }
   ```

After you create this initial scaffold, you must refactor your code to use the web worker by sending messages to and from the worker.

**IMPORTANT:** Some environments or platforms, such as `@angular/platform-server` used in [Server-side Rendering](guide/ssr), don't support web workers.

To ensure that your application works in these environments, you must provide a fallback mechanism to perform the computations that the worker would otherwise perform.

----
url: https://angular.dev/api/cdk/testing/LocatorFactory
----

```
    
      interface LocatorFactory {}
    
    
```

Gets a locator factory rooted at the document root.

@returns`LocatorFactory`

The root element of this [`LocatorFactory`](/api/cdk/testing/LocatorFactory) as a [`TestElement`](/api/cdk/testing/TestElement).

Creates an asynchronous locator function that can be used to find a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instance or element under the root element of this [`LocatorFactory`](/api/cdk/testing/LocatorFactory).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await lf.locatorFor(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await lf.locatorFor('div', DivHarness)() // Gets a `TestElement` instance for #d1
await lf.locatorFor('span')()            // Throws because the `Promise` rejects
```

@paramqueries`T`

A list of queries specifying which harnesses and elements to search for:

* A `string` searches for elements matching the CSS selector specified by the string.
* A [`ComponentHarness`](/api/cdk/testing/ComponentHarness) constructor searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given class.
* A [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given predicate.

@returns`() => Promise<LocatorFnResult<T>>`

Creates an asynchronous locator function that can be used to find a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instance or element under the root element of this [`LocatorFactory`](/api/cdk/testing/LocatorFactory).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await lf.locatorForOptional(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await lf.locatorForOptional('div', DivHarness)() // Gets a `TestElement` instance for #d1
await lf.locatorForOptional('span')()            // Gets `null`
```

@paramqueries`T`

A list of queries specifying which harnesses and elements to search for:

* A `string` searches for elements matching the CSS selector specified by the string.
* A [`ComponentHarness`](/api/cdk/testing/ComponentHarness) constructor searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given class.
* A [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given predicate.

@returns`() => Promise<LocatorFnResult<T> | null>`

Creates an asynchronous locator function that can be used to find [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances or elements under the root element of this [`LocatorFactory`](/api/cdk/testing/LocatorFactory).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'` and `IdIsD1Harness.hostSelector` is `'#d1'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
// Gets [DivHarness for #d1, TestElement for #d1, DivHarness for #d2, TestElement for #d2]
await lf.locatorForAll(DivHarness, 'div')()
// Gets [TestElement for #d1, TestElement for #d2]
await lf.locatorForAll('div', '#d1')()
// Gets [DivHarness for #d1, IdIsD1Harness for #d1, DivHarness for #d2]
await lf.locatorForAll(DivHarness, IdIsD1Harness)()
// Gets []
await lf.locatorForAll('span')()
```

Gets a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance for an element under the root of this [`LocatorFactory`](/api/cdk/testing/LocatorFactory).

@paramselector`string`

The selector for the root element.

@returns`Promise<HarnessLoader>`

Gets a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance for an element under the root of this [`LocatorFactory`](/api/cdk/testing/LocatorFactory)

Flushes change detection and async tasks captured in the Angular zone. In most cases it should not be necessary to call this manually. However, there may be some edge cases where it is needed to fully flush animation events.

@returns`Promise<void>`

Waits for all scheduled or running async tasks to complete. This allows harness authors to wait for async tasks outside of the Angular zone.

@returns`Promise<void>`

Jump to details

----
url: https://angular.dev/api/core/InputSignalWithTransform
----

```
    
      interface InputSignalWithTransform<T, TransformT> extends Signal<T> {}
    
    
```

## [Description](#description)

[`InputSignalWithTransform`](/api/core/InputSignalWithTransform) represents a special [`Signal`](/api/core/Signal) for a directive/component input with a `transform` function.

Signal inputs with transforms capture an extra generic for their transform write type. Transforms can expand the accepted bound values for an input while ensuring value retrievals of the signal input are still matching the generic input type.

```
class MyDir {
  disabled = input(false, {
    transform: (v: string|boolean) => convertToBoolean(v),
  }); // InputSignalWithTransform<boolean, string|boolean>

  click() {
    this.disabled() // always returns a `boolean`.
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/declareExperimentalWebMcpTool
----

```
function declareExperimentalWebMcpTool<
  InputSchema extends JsonSchemaForInference,
>(
  tool: ToolDescriptor<InputSchema>,
  injector?: Injector | undefined,
): Promise<void>;
```

@paramtool`ToolDescriptor<InputSchema>`

The tool to register and execute when invoked by an AI agent.

@paraminjector`Injector | undefined`

Optional [`Injector`](/api/core/Injector) which will automatically unregister the tool when destroyed. Defaults to the current injection context if not provided.

@returns`Promise<void>`

## [Description](#description)

Declares a WebMCP tool.

The tool is immediately registered and automatically unregistered when the associated injection context is destroyed.

The `tool.execute` function is invoked in the injection context of the provided [`Injector`](/api/core/Injector), or the injection context of [`declareExperimentalWebMcpTool`](/api/core/declareExperimentalWebMcpTool) itself.

Jump to details

----
url: https://angular.dev/api
----

search

arrow\_drop\_down

Filter by Identifier type

* BBlock
* CClass
* KConst
* @Decorator
* DDirective
* ElElement
* EEnum
* FFunction
* IInterface
* PPipe
* MModule
* TType Alias
* IAInitializer API

- [KACCORDION\_GROUP](/api/aria/accordion/ACCORDION_GROUP)
- [CAccordionContent](/api/aria/accordion/AccordionContent)
- [DAccordionGroup](/api/aria/accordion/AccordionGroup)
- [CAccordionPanel](/api/aria/accordion/AccordionPanel)
- [DAccordionTrigger](/api/aria/accordion/AccordionTrigger)

* [DCombobox](/api/aria/combobox/Combobox)
* [KCOMBOBOX\_POPUP](/api/aria/combobox/COMBOBOX_POPUP)
* [CComboboxPopup](/api/aria/combobox/ComboboxPopup)
* [DComboboxWidget](/api/aria/combobox/ComboboxWidget)

- [DGrid](/api/aria/grid/Grid)
- [KGRID](/api/aria/grid/GRID)
- [KGRID\_CELL](/api/aria/grid/GRID_CELL)
- [KGRID\_ROW](/api/aria/grid/GRID_ROW)
- [DGridCell](/api/aria/grid/GridCell)
- [DGridCellWidget](/api/aria/grid/GridCellWidget)
- [DGridRow](/api/aria/grid/GridRow)

* [DListbox](/api/aria/listbox/Listbox)
* [KLISTBOX](/api/aria/listbox/LISTBOX)
* [DOption](/api/aria/listbox/Option)

- [CMenu](/api/aria/menu/Menu)
- [KMENU\_COMPONENT](/api/aria/menu/MENU_COMPONENT)
- [DMenuBar](/api/aria/menu/MenuBar)
- [CMenuContent](/api/aria/menu/MenuContent)
- [DMenuItem](/api/aria/menu/MenuItem)
- [DMenuTrigger](/api/aria/menu/MenuTrigger)

* [DTab](/api/aria/tabs/Tab)
* [KTAB\_LIST](/api/aria/tabs/TAB_LIST)
* [CTabContent](/api/aria/tabs/TabContent)
* [DTabList](/api/aria/tabs/TabList)
* [CTabPanel](/api/aria/tabs/TabPanel)
* [DTabs](/api/aria/tabs/Tabs)
* [KTABS](/api/aria/tabs/TABS)

- [DToolbar](/api/aria/toolbar/Toolbar)
- [KTOOLBAR\_WIDGET\_GROUP](/api/aria/toolbar/TOOLBAR_WIDGET_GROUP)
- [DToolbarWidget](/api/aria/toolbar/ToolbarWidget)
- [DToolbarWidgetGroup](/api/aria/toolbar/ToolbarWidgetGroup)

* [DTree](/api/aria/tree/Tree)
* [DTreeItem](/api/aria/tree/TreeItem)
* [CTreeItemGroup](/api/aria/tree/TreeItemGroup)

- [KCDK\_DRAG\_CONFIG](/api/cdk/drag-drop/CDK_DRAG_CONFIG)
- [KCDK\_DRAG\_HANDLE](/api/cdk/drag-drop/CDK_DRAG_HANDLE)
- [KCDK\_DRAG\_PLACEHOLDER](/api/cdk/drag-drop/CDK_DRAG_PLACEHOLDER)
- [KCDK\_DRAG\_PREVIEW](/api/cdk/drag-drop/CDK_DRAG_PREVIEW)
- [KCDK\_DROP\_LIST](/api/cdk/drag-drop/CDK_DROP_LIST)
- [KCDK\_DROP\_LIST\_GROUP](/api/cdk/drag-drop/CDK_DROP_LIST_GROUP)
- [CCdkDrag](/api/cdk/drag-drop/CdkDrag)
- [ICdkDragDrop](/api/cdk/drag-drop/CdkDragDrop)
- [ICdkDragEnd](/api/cdk/drag-drop/CdkDragEnd)
- [ICdkDragEnter](/api/cdk/drag-drop/CdkDragEnter)
- [ICdkDragExit](/api/cdk/drag-drop/CdkDragExit)
- [CCdkDragHandle](/api/cdk/drag-drop/CdkDragHandle)
- [ICdkDragMove](/api/cdk/drag-drop/CdkDragMove)
- [DCdkDragPlaceholder](/api/cdk/drag-drop/CdkDragPlaceholder)
- [CCdkDragPreview](/api/cdk/drag-drop/CdkDragPreview)
- [ICdkDragRelease](/api/cdk/drag-drop/CdkDragRelease)
- [ICdkDragSortEvent](/api/cdk/drag-drop/CdkDragSortEvent)
- [ICdkDragStart](/api/cdk/drag-drop/CdkDragStart)
- [CCdkDropList](/api/cdk/drag-drop/CdkDropList)
- [CCdkDropListGroup](/api/cdk/drag-drop/CdkDropListGroup)
- [FcopyArrayItem](/api/cdk/drag-drop/copyArrayItem)
- [FcreateDragRef](/api/cdk/drag-drop/createDragRef)
- [FcreateDropListRef](/api/cdk/drag-drop/createDropListRef)
- [TDragAxis](/api/cdk/drag-drop/DragAxis)
- [TDragConstrainPosition](/api/cdk/drag-drop/DragConstrainPosition)
- [IDragDropConfig](/api/cdk/drag-drop/DragDropConfig)
- [CDragDropModule](/api/cdk/drag-drop/DragDropModule)
- [CDragRef](/api/cdk/drag-drop/DragRef)
- [IDragRefConfig](/api/cdk/drag-drop/DragRefConfig)
- [TDragStartDelay](/api/cdk/drag-drop/DragStartDelay)
- [TDropListOrientation](/api/cdk/drag-drop/DropListOrientation)
- [CDropListRef](/api/cdk/drag-drop/DropListRef)
- [FmoveItemInArray](/api/cdk/drag-drop/moveItemInArray)
- [IPoint](/api/cdk/drag-drop/Point)
- [TPreviewContainer](/api/cdk/drag-drop/PreviewContainer)
- [FtransferArrayItem](/api/cdk/drag-drop/transferArrayItem)

* [TAsyncOptionPredicate](/api/cdk/testing/AsyncOptionPredicate)
* [TAsyncPredicate](/api/cdk/testing/AsyncPredicate)
* [IAutoChangeDetectionStatus](/api/cdk/testing/AutoChangeDetectionStatus)
* [IBaseHarnessFilters](/api/cdk/testing/BaseHarnessFilters)
* [CComponentHarness](/api/cdk/testing/ComponentHarness)
* [IComponentHarnessConstructor](/api/cdk/testing/ComponentHarnessConstructor)
* [CContentContainerComponentHarness](/api/cdk/testing/ContentContainerComponentHarness)
* [IElementDimensions](/api/cdk/testing/ElementDimensions)
* [TEventData](/api/cdk/testing/EventData)
* [FhandleAutoChangeDetectionStatus](/api/cdk/testing/handleAutoChangeDetectionStatus)
* [CHarnessEnvironment](/api/cdk/testing/HarnessEnvironment)
* [IHarnessLoader](/api/cdk/testing/HarnessLoader)
* [CHarnessPredicate](/api/cdk/testing/HarnessPredicate)
* [THarnessQuery](/api/cdk/testing/HarnessQuery)
* [ILocatorFactory](/api/cdk/testing/LocatorFactory)
* [TLocatorFnResult](/api/cdk/testing/LocatorFnResult)
* [FmanualChangeDetection](/api/cdk/testing/manualChangeDetection)
* [IModifierKeys](/api/cdk/testing/ModifierKeys)
* [Fparallel](/api/cdk/testing/parallel)
* [FstopHandlingAutoChangeDetectionStatus](/api/cdk/testing/stopHandlingAutoChangeDetectionStatus)
* [ITestElement](/api/cdk/testing/TestElement)
* [ETestKey](/api/cdk/testing/TestKey)
* [ITextOptions](/api/cdk/testing/TextOptions)

- [CSeleniumWebDriverElement](/api/cdk/testing/selenium-webdriver/SeleniumWebDriverElement)
- [CSeleniumWebDriverHarnessEnvironment](/api/cdk/testing/selenium-webdriver/SeleniumWebDriverHarnessEnvironment)
- [FwaitForAngularReady](/api/cdk/testing/selenium-webdriver/waitForAngularReady)
- [IWebDriverHarnessEnvironmentOptions](/api/cdk/testing/selenium-webdriver/WebDriverHarnessEnvironmentOptions)

* [TFixture](/api/cdk/testing/testbed/Fixture)
* [CTestbedHarnessEnvironment](/api/cdk/testing/testbed/TestbedHarnessEnvironment)
* [ITestbedHarnessEnvironmentOptions](/api/cdk/testing/testbed/TestbedHarnessEnvironmentOptions)
* [CUnitTestElement](/api/cdk/testing/testbed/UnitTestElement)

- [KAPP\_BASE\_HREF](/api/common/APP_BASE_HREF)
- [PAsyncPipe](/api/common/AsyncPipe)
- [CBrowserPlatformLocation](/api/common/BrowserPlatformLocation)
- [MCommonModule](/api/common/CommonModule)
- [PCurrencyPipe](/api/common/CurrencyPipe)
- [KDATE\_PIPE\_DEFAULT\_OPTIONS](/api/common/DATE_PIPE_DEFAULT_OPTIONS)
- [PDatePipe](/api/common/DatePipe)
- [IDatePipeConfig](/api/common/DatePipeConfig)
- [PDecimalPipe](/api/common/DecimalPipe)
- [KDOCUMENT](/api/common/DOCUMENT)
- [FformatCurrency](/api/common/formatCurrency)
- [FformatDate](/api/common/formatDate)
- [FformatNumber](/api/common/formatNumber)
- [FformatPercent](/api/common/formatPercent)
- [CHashLocationStrategy](/api/common/HashLocationStrategy)
- [PI18nPluralPipe](/api/common/I18nPluralPipe)
- [PI18nSelectPipe](/api/common/I18nSelectPipe)
- [KIMAGE\_CONFIG](/api/common/IMAGE_CONFIG)
- [KIMAGE\_LOADER](/api/common/IMAGE_LOADER)
- [TImageConfig](/api/common/ImageConfig)
- [TImageLoader](/api/common/ImageLoader)
- [IImageLoaderConfig](/api/common/ImageLoaderConfig)
- [IImagePlaceholderConfig](/api/common/ImagePlaceholderConfig)
- [FisPlatformBrowser](/api/common/isPlatformBrowser)
- [FisPlatformServer](/api/common/isPlatformServer)
- [PJsonPipe](/api/common/JsonPipe)
- [IKeyValue](/api/common/KeyValue)
- [PKeyValuePipe](/api/common/KeyValuePipe)
- [CLocation](/api/common/Location)
- [KLOCATION\_INITIALIZED](/api/common/LOCATION_INITIALIZED)
- [ILocationChangeEvent](/api/common/LocationChangeEvent)
- [ILocationChangeListener](/api/common/LocationChangeListener)
- [CLocationStrategy](/api/common/LocationStrategy)
- [PLowerCasePipe](/api/common/LowerCasePipe)
- [DNgClass](/api/common/NgClass)
- [DNgComponentOutlet](/api/common/NgComponentOutlet)
- [CNgLocaleLocalization](/api/common/NgLocaleLocalization)
- [CNgLocalization](/api/common/NgLocalization)
- [DNgOptimizedImage](/api/common/NgOptimizedImage)
- [DNgPlural](/api/common/NgPlural)
- [DNgPluralCase](/api/common/NgPluralCase)
- [DNgStyle](/api/common/NgStyle)
- [DNgTemplateOutlet](/api/common/NgTemplateOutlet)
- [CNoTrailingSlashPathLocationStrategy](/api/common/NoTrailingSlashPathLocationStrategy)
- [CPathLocationStrategy](/api/common/PathLocationStrategy)
- [PPercentPipe](/api/common/PercentPipe)
- [CPlatformLocation](/api/common/PlatformLocation)
- [CPlatformNavigation](/api/common/PlatformNavigation) EXP
- [IPopStateEvent](/api/common/PopStateEvent)
- [KPRECONNECT\_CHECK\_BLOCKLIST](/api/common/PRECONNECT_CHECK_BLOCKLIST)
- [KprovideCloudflareLoader](/api/common/provideCloudflareLoader)
- [KprovideCloudinaryLoader](/api/common/provideCloudinaryLoader)
- [KprovideImageKitLoader](/api/common/provideImageKitLoader)
- [KprovideImgixLoader](/api/common/provideImgixLoader)
- [FprovideNetlifyLoader](/api/common/provideNetlifyLoader)
- [FregisterLocaleData](/api/common/registerLocaleData)
- [PSlicePipe](/api/common/SlicePipe)
- [PTitleCasePipe](/api/common/TitleCasePipe)
- [CTrailingSlashPathLocationStrategy](/api/common/TrailingSlashPathLocationStrategy)
- [PUpperCasePipe](/api/common/UpperCasePipe)
- [KVERSION](/api/common/VERSION)
- [CViewportScroller](/api/common/ViewportScroller)
- [CXhrFactory](/api/common/XhrFactory)

* [CFetchBackend](/api/common/http/FetchBackend)
* [KHTTP\_INTERCEPTORS](/api/common/http/HTTP_INTERCEPTORS)
* [KHTTP\_TRANSFER\_CACHE\_ORIGIN\_MAP](/api/common/http/HTTP_TRANSFER_CACHE_ORIGIN_MAP)
* [CHttpBackend](/api/common/http/HttpBackend)
* [CHttpClient](/api/common/http/HttpClient)
* [IHttpClientCommonOptions](/api/common/http/HttpClientCommonOptions)
* [CHttpContext](/api/common/http/HttpContext)
* [CHttpContextToken](/api/common/http/HttpContextToken)
* [IHttpDownloadProgressEvent](/api/common/http/HttpDownloadProgressEvent)
* [CHttpErrorResponse](/api/common/http/HttpErrorResponse)
* [THttpEvent](/api/common/http/HttpEvent)
* [EHttpEventType](/api/common/http/HttpEventType)
* [IHttpFeature](/api/common/http/HttpFeature)
* [EHttpFeatureKind](/api/common/http/HttpFeatureKind)
* [CHttpHandler](/api/common/http/HttpHandler)
* [THttpHandlerFn](/api/common/http/HttpHandlerFn)
* [CHttpHeaderResponse](/api/common/http/HttpHeaderResponse)
* [CHttpHeaders](/api/common/http/HttpHeaders)
* [IHttpInterceptor](/api/common/http/HttpInterceptor)
* [THttpInterceptorFn](/api/common/http/HttpInterceptorFn)
* [IHttpParameterCodec](/api/common/http/HttpParameterCodec)
* [CHttpParams](/api/common/http/HttpParams)
* [IHttpParamsOptions](/api/common/http/HttpParamsOptions)
* [IHttpProgressEvent](/api/common/http/HttpProgressEvent)
* [CHttpRequest](/api/common/http/HttpRequest)
* [IHttpRequestOptions](/api/common/http/HttpRequestOptions)
* [KhttpResource](/api/common/http/httpResource)
* [IHttpResourceFn](/api/common/http/HttpResourceFn)
* [IHttpResourceOptions](/api/common/http/HttpResourceOptions)
* [IHttpResourceRef](/api/common/http/HttpResourceRef)
* [IHttpResourceRequest](/api/common/http/HttpResourceRequest)
* [CHttpResponse](/api/common/http/HttpResponse)
* [CHttpResponseBase](/api/common/http/HttpResponseBase)
* [IHttpSentEvent](/api/common/http/HttpSentEvent)
* [EHttpStatusCode](/api/common/http/HttpStatusCode)
* [IHttpTransferCacheOptions](/api/common/http/HttpTransferCacheOptions)
* [IHttpUploadProgressEvent](/api/common/http/HttpUploadProgressEvent)
* [CHttpUrlEncodingCodec](/api/common/http/HttpUrlEncodingCodec)
* [IHttpUserEvent](/api/common/http/HttpUserEvent)
* [CHttpXhrBackend](/api/common/http/HttpXhrBackend)
* [CHttpXsrfTokenExtractor](/api/common/http/HttpXsrfTokenExtractor)
* [FprovideHttpClient](/api/common/http/provideHttpClient)
* [FwithInterceptors](/api/common/http/withInterceptors)
* [FwithInterceptorsFromDi](/api/common/http/withInterceptorsFromDi)
* [FwithNoXsrfProtection](/api/common/http/withNoXsrfProtection)
* [FwithRequestsMadeViaParent](/api/common/http/withRequestsMadeViaParent)
* [FwithXhr](/api/common/http/withXhr)
* [FwithXsrfConfiguration](/api/common/http/withXsrfConfiguration)

- [CHttpTestingController](/api/common/http/testing/HttpTestingController)
- [FprovideHttpClientTesting](/api/common/http/testing/provideHttpClientTesting)
- [IRequestMatch](/api/common/http/testing/RequestMatch)
- [CTestRequest](/api/common/http/testing/TestRequest)

* [KMOCK\_PLATFORM\_LOCATION\_CONFIG](/api/common/testing/MOCK_PLATFORM_LOCATION_CONFIG)
* [CMockLocationStrategy](/api/common/testing/MockLocationStrategy)
* [CMockPlatformLocation](/api/common/testing/MockPlatformLocation)
* [IMockPlatformLocationConfig](/api/common/testing/MockPlatformLocationConfig)
* [FprovideLocationMocks](/api/common/testing/provideLocationMocks)
* [CSpyLocation](/api/common/testing/SpyLocation)

- [C$locationShim](/api/common/upgrade/$locationShim)
- [C$locationShimProvider](/api/common/upgrade/$locationShimProvider)
- [CAngularJSUrlCodec](/api/common/upgrade/AngularJSUrlCodec)
- [KLOCATION\_UPGRADE\_CONFIGURATION](/api/common/upgrade/LOCATION_UPGRADE_CONFIGURATION)
- [ILocationUpgradeConfig](/api/common/upgrade/LocationUpgradeConfig)
- [MLocationUpgradeModule](/api/common/upgrade/LocationUpgradeModule)
- [CUrlCodec](/api/common/upgrade/UrlCodec)

* [B@boundary](/api/core/@boundary)
* [B@defer](/api/core/@defer)
* [B@for](/api/core/@for)
* [B@if](/api/core/@if)
* [B@let](/api/core/@let)
* [B@switch](/api/core/@switch)
* [IAbstractType](/api/core/AbstractType)
* [IAfterContentChecked](/api/core/AfterContentChecked)
* [IAfterContentInit](/api/core/AfterContentInit)
* [FafterEveryRender](/api/core/afterEveryRender)
* [FafterNextRender](/api/core/afterNextRender)
* [FafterRenderEffect](/api/core/afterRenderEffect)
* [IAfterRenderEffectOptions](/api/core/AfterRenderEffectOptions)
* [IAfterRenderOptions](/api/core/AfterRenderOptions)
* [IAfterRenderRef](/api/core/AfterRenderRef)
* [IAfterViewChecked](/api/core/AfterViewChecked)
* [IAfterViewInit](/api/core/AfterViewInit)
* [KANIMATION\_MODULE\_TYPE](/api/core/ANIMATION_MODULE_TYPE)
* [TAnimationCallbackEvent](/api/core/AnimationCallbackEvent)
* [TAnimationFunction](/api/core/AnimationFunction)
* [KAPP\_BOOTSTRAP\_LISTENER](/api/core/APP_BOOTSTRAP_LISTENER)
* [KAPP\_ID](/api/core/APP_ID)
* [IApplicationConfig](/api/core/ApplicationConfig)
* [CApplicationInitStatus](/api/core/ApplicationInitStatus)
* [MApplicationModule](/api/core/ApplicationModule)
* [CApplicationRef](/api/core/ApplicationRef)
* [FasNativeElements](/api/core/asNativeElements)
* [FassertInInjectionContext](/api/core/assertInInjectionContext)
* [FassertNotInReactiveContext](/api/core/assertNotInReactiveContext)
* [FassertPlatform](/api/core/assertPlatform)
* [@Attribute](/api/core/Attribute)
* [IBaseResourceOptions](/api/core/BaseResourceOptions)
* [IBinding](/api/core/Binding)
* [FbooleanAttribute](/api/core/booleanAttribute)
* [EChangeDetectionStrategy](/api/core/ChangeDetectionStrategy)
* [CChangeDetectorRef](/api/core/ChangeDetectorRef)
* [IClassProvider](/api/core/ClassProvider)
* [IClassSansProvider](/api/core/ClassSansProvider)
* [KCOMPILER\_OPTIONS](/api/core/COMPILER_OPTIONS)
* [TCompilerOptions](/api/core/CompilerOptions)
* [@Component](/api/core/Component)
* [IComponentMirror](/api/core/ComponentMirror)
* [CComponentRef](/api/core/ComponentRef)
* [Fcomputed](/api/core/computed)
* [IConstructorProvider](/api/core/ConstructorProvider)
* [IConstructorSansProvider](/api/core/ConstructorSansProvider)
* [IAcontentChild](/api/core/contentChild)
* [@ContentChild](/api/core/ContentChild)
* [IAcontentChildren](/api/core/contentChildren)
* [@ContentChildren](/api/core/ContentChildren)
* [FcreateComponent](/api/core/createComponent)
* [ICreateComputedOptions](/api/core/CreateComputedOptions)
* [ICreateEffectOptions](/api/core/CreateEffectOptions)
* [FcreateEnvironmentInjector](/api/core/createEnvironmentInjector)
* [FcreateNgModule](/api/core/createNgModule)
* [FcreatePlatform](/api/core/createPlatform)
* [FcreatePlatformFactory](/api/core/createPlatformFactory)
* [ICreateSignalOptions](/api/core/CreateSignalOptions)
* [KCSP\_NONCE](/api/core/CSP_NONCE)
* [KCUSTOM\_ELEMENTS\_SCHEMA](/api/core/CUSTOM_ELEMENTS_SCHEMA)
* [Fdebounced](/api/core/debounced) EXP
* [IDebouncedOptions](/api/core/DebouncedOptions) EXP
* [TDebounceTimer](/api/core/DebounceTimer) EXP
* [CDebugElement](/api/core/DebugElement)
* [CDebugEventListener](/api/core/DebugEventListener)
* [CDebugNode](/api/core/DebugNode)
* [FdeclareExperimentalWebMcpTool](/api/core/declareExperimentalWebMcpTool) EXP
* [KDEFAULT\_CURRENCY\_CODE](/api/core/DEFAULT_CURRENCY_CODE)
* [IDefaultExport](/api/core/DefaultExport)
* [IDestroyableInjector](/api/core/DestroyableInjector)
* [FdestroyPlatform](/api/core/destroyPlatform)
* [CDestroyRef](/api/core/DestroyRef)
* [@Directive](/api/core/Directive)
* [IDirectiveWithBindings](/api/core/DirectiveWithBindings)
* [IDoBootstrap](/api/core/DoBootstrap)
* [IDoCheck](/api/core/DoCheck)
* [KDOCUMENT](/api/core/DOCUMENT)
* [Feffect](/api/core/effect)
* [TEffectCleanupFn](/api/core/EffectCleanupFn)
* [TEffectCleanupRegisterFn](/api/core/EffectCleanupRegisterFn)
* [IEffectRef](/api/core/EffectRef)
* [CElementRef](/api/core/ElementRef)
* [CEmbeddedViewRef](/api/core/EmbeddedViewRef)
* [FenableProdMode](/api/core/enableProdMode)
* [FenableProfiling](/api/core/enableProfiling)
* [CEnvironmentInjector](/api/core/EnvironmentInjector)
* [TEnvironmentProviders](/api/core/EnvironmentProviders)
* [IErrorDetails](/api/core/ErrorDetails) DEV
* [CErrorHandler](/api/core/ErrorHandler)
* [IEventEmitter](/api/core/EventEmitter)
* [IExistingProvider](/api/core/ExistingProvider)
* [IExistingSansProvider](/api/core/ExistingSansProvider)
* [IFactoryProvider](/api/core/FactoryProvider)
* [IFactorySansProvider](/api/core/FactorySansProvider)
* [FforwardRef](/api/core/forwardRef)
* [IForwardRefFn](/api/core/ForwardRefFn)
* [FgetDebugNode](/api/core/getDebugNode)
* [FgetNgModuleById](/api/core/getNgModuleById)
* [FgetPlatform](/api/core/getPlatform)
* [IGetTestability](/api/core/GetTestability)
* [@Host](/api/core/Host)
* [KHOST\_TAG\_NAME](/api/core/HOST_TAG_NAME)
* [CHostAttributeToken](/api/core/HostAttributeToken)
* [@HostBinding](/api/core/HostBinding)
* [@HostListener](/api/core/HostListener)
* [IIdleService](/api/core/IdleService)
* [FimportProvidersFrom](/api/core/importProvidersFrom)
* [TImportProvidersSource](/api/core/ImportProvidersSource)
* [Finject](/api/core/inject)
* [@Inject](/api/core/Inject)
* [@Injectable](/api/core/Injectable)
* [TInjectableProvider](/api/core/InjectableProvider)
* [IInjectableType](/api/core/InjectableType)
* [FinjectAsync](/api/core/injectAsync)
* [IInjectAsyncOptions](/api/core/InjectAsyncOptions)
* [CInjectionToken](/api/core/InjectionToken)
* [IInjectOptions](/api/core/InjectOptions)
* [CInjector](/api/core/Injector)
* [KINJECTOR](/api/core/INJECTOR)
* [IInjectorType](/api/core/InjectorType)
* [IAinput](/api/core/input)
* [@Input](/api/core/Input)
* [FinputBinding](/api/core/inputBinding)
* [IInputOptions](/api/core/InputOptions)
* [TInputOptionsWithoutTransform](/api/core/InputOptionsWithoutTransform)
* [TInputOptionsWithTransform](/api/core/InputOptionsWithTransform)
* [IInputSignal](/api/core/InputSignal)
* [IInputSignalWithTransform](/api/core/InputSignalWithTransform)
* [FisDevMode](/api/core/isDevMode)
* [FisSignal](/api/core/isSignal)
* [FisStandalone](/api/core/isStandalone)
* [FisWritableSignal](/api/core/isWritableSignal)
* [IIterableChangeRecord](/api/core/IterableChangeRecord)
* [IIterableChanges](/api/core/IterableChanges)
* [IIterableDiffer](/api/core/IterableDiffer)
* [IIterableDifferFactory](/api/core/IterableDifferFactory)
* [CIterableDiffers](/api/core/IterableDiffers)
* [IKeyValueChangeRecord](/api/core/KeyValueChangeRecord)
* [IKeyValueChanges](/api/core/KeyValueChanges)
* [IKeyValueDiffer](/api/core/KeyValueDiffer)
* [IKeyValueDifferFactory](/api/core/KeyValueDifferFactory)
* [CKeyValueDiffers](/api/core/KeyValueDiffers)
* [FlinkedSignal](/api/core/linkedSignal)
* [IListenerOptions](/api/core/ListenerOptions)
* [KLOCALE\_ID](/api/core/LOCALE_ID)
* [FmakeEnvironmentProviders](/api/core/makeEnvironmentProviders)
* [FmakeStateKey](/api/core/makeStateKey)
* [KMAX\_ANIMATION\_TIMEOUT](/api/core/MAX_ANIMATION_TIMEOUT)
* [FmergeApplicationConfig](/api/core/mergeApplicationConfig)
* [EMissingTranslationStrategy](/api/core/MissingTranslationStrategy)
* [IAmodel](/api/core/model)
* [IModelOptions](/api/core/ModelOptions)
* [IModelSignal](/api/core/ModelSignal)
* [IModuleWithProviders](/api/core/ModuleWithProviders)
* [Elng-container](/api/core/ng-container)
* [Elng-content](/api/core/ng-content)
* [Elng-template](/api/core/ng-template)
* [TNgIterable](/api/core/NgIterable)
* [@NgModule](/api/core/NgModule)
* [CNgModuleRef](/api/core/NgModuleRef)
* [CNgZone](/api/core/NgZone)
* [INgZoneOptions](/api/core/NgZoneOptions)
* [KNO\_ERRORS\_SCHEMA](/api/core/NO_ERRORS_SCHEMA)
* [FnumberAttribute](/api/core/numberAttribute)
* [IOnChanges](/api/core/OnChanges)
* [IOnDestroy](/api/core/OnDestroy)
* [FonIdle](/api/core/onIdle)
* [IOnInit](/api/core/OnInit)
* [@Optional](/api/core/Optional)
* [IAoutput](/api/core/output)
* [@Output](/api/core/Output)
* [FoutputBinding](/api/core/outputBinding)
* [COutputEmitterRef](/api/core/OutputEmitterRef)
* [IOutputOptions](/api/core/OutputOptions)
* [IOutputRef](/api/core/OutputRef)
* [IOutputRefSubscription](/api/core/OutputRefSubscription)
* [CPendingTasks](/api/core/PendingTasks)
* [@Pipe](/api/core/Pipe)
* [IPipeTransform](/api/core/PipeTransform)
* [KPLATFORM\_ID](/api/core/PLATFORM_ID)
* [KplatformCore](/api/core/platformCore)
* [CPlatformRef](/api/core/PlatformRef)
* [TPredicate](/api/core/Predicate)
* [TPrefetchTrigger](/api/core/PrefetchTrigger)
* [IPromiseResourceOptions](/api/core/PromiseResourceOptions)
* [FprovideAppInitializer](/api/core/provideAppInitializer)
* [FprovideBrowserGlobalErrorListeners](/api/core/provideBrowserGlobalErrorListeners)
* [FprovideCheckNoChangesConfig](/api/core/provideCheckNoChangesConfig) DEV
* [FprovideEnvironmentInitializer](/api/core/provideEnvironmentInitializer)
* [FprovideExperimentalWebMcpTools](/api/core/provideExperimentalWebMcpTools) EXP
* [FprovideIdleServiceWith](/api/core/provideIdleServiceWith)
* [FprovideNgReflectAttributes](/api/core/provideNgReflectAttributes)
* [FprovidePlatformInitializer](/api/core/providePlatformInitializer)
* [TProvider](/api/core/Provider)
* [TProviderToken](/api/core/ProviderToken)
* [FprovideStabilityDebugging](/api/core/provideStabilityDebugging)
* [FprovideZoneChangeDetection](/api/core/provideZoneChangeDetection)
* [FprovideZonelessChangeDetection](/api/core/provideZonelessChangeDetection)
* [IQuery](/api/core/Query)
* [CQueryList](/api/core/QueryList)
* [FreflectComponentType](/api/core/reflectComponentType)
* [CRenderer2](/api/core/Renderer2)
* [CRendererFactory2](/api/core/RendererFactory2)
* [ERendererStyleFlags2](/api/core/RendererStyleFlags2)
* [IRendererType2](/api/core/RendererType2)
* [KREQUEST](/api/core/REQUEST)
* [KREQUEST\_CONTEXT](/api/core/REQUEST_CONTEXT)
* [FresolveForwardRef](/api/core/resolveForwardRef)
* [Fresource](/api/core/resource)
* [IResource](/api/core/Resource)
* [CResourceDependencyError](/api/core/ResourceDependencyError)
* [FresourceFromSnapshots](/api/core/resourceFromSnapshots) EXP
* [TResourceLoader](/api/core/ResourceLoader)
* [IResourceLoaderParams](/api/core/ResourceLoaderParams)
* [TResourceOptions](/api/core/ResourceOptions)
* [IResourceParamsContext](/api/core/ResourceParamsContext)
* [CResourceParamsStatus](/api/core/ResourceParamsStatus)
* [IResourceRef](/api/core/ResourceRef)
* [TResourceSnapshot](/api/core/ResourceSnapshot)
* [TResourceStatus](/api/core/ResourceStatus)
* [TResourceStreamingLoader](/api/core/ResourceStreamingLoader)
* [TResourceStreamItem](/api/core/ResourceStreamItem)
* [KRESPONSE\_INIT](/api/core/RESPONSE_INIT)
* [TResponseInit](/api/core/ResponseInit)
* [FrunInInjectionContext](/api/core/runInInjectionContext)
* [CSanitizer](/api/core/Sanitizer)
* [ISchemaMetadata](/api/core/SchemaMetadata)
* [ESecurityContext](/api/core/SecurityContext)
* [@Self](/api/core/Self)
* [@Service](/api/core/Service)
* [FsetTestabilityGetter](/api/core/setTestabilityGetter)
* [Fsignal](/api/core/signal)
* [TSignal](/api/core/Signal)
* [CSimpleChange](/api/core/SimpleChange)
* [TSimpleChanges](/api/core/SimpleChanges)
* [@SkipSelf](/api/core/SkipSelf)
* [TStateKey](/api/core/StateKey)
* [IStaticClassProvider](/api/core/StaticClassProvider)
* [IStaticClassSansProvider](/api/core/StaticClassSansProvider)
* [TStaticProvider](/api/core/StaticProvider)
* [IStreamingResourceOptions](/api/core/StreamingResourceOptions)
* [CTemplateRef](/api/core/TemplateRef)
* [CTestability](/api/core/Testability)
* [CTestabilityRegistry](/api/core/TestabilityRegistry)
* [ITrackByFunction](/api/core/TrackByFunction)
* [CTransferState](/api/core/TransferState)
* [KTRANSLATIONS](/api/core/TRANSLATIONS)
* [KTRANSLATIONS\_FORMAT](/api/core/TRANSLATIONS_FORMAT)
* [FtwoWayBinding](/api/core/twoWayBinding)
* [KType](/api/core/Type)
* [ITypeProvider](/api/core/TypeProvider)
* [Funtracked](/api/core/untracked)
* [TValueEqualityFn](/api/core/ValueEqualityFn)
* [IValueProvider](/api/core/ValueProvider)
* [IValueSansProvider](/api/core/ValueSansProvider)
* [CVersion](/api/core/Version)
* [KVERSION](/api/core/VERSION)
* [IAviewChild](/api/core/viewChild)
* [@ViewChild](/api/core/ViewChild)
* [IAviewChildren](/api/core/viewChildren)
* [@ViewChildren](/api/core/ViewChildren)
* [CViewContainerRef](/api/core/ViewContainerRef)
* [EViewEncapsulation](/api/core/ViewEncapsulation)
* [CViewRef](/api/core/ViewRef)
* [IWebMcpClient](/api/core/WebMcpClient) EXP
* [IWebMcpToolDescriptor](/api/core/WebMcpToolDescriptor) EXP
* [TWebMcpToolExecute](/api/core/WebMcpToolExecute) EXP
* [IWritableResource](/api/core/WritableResource)
* [IWritableSignal](/api/core/WritableSignal)

- [IAoutputFromObservable](/api/core/rxjs-interop/outputFromObservable)
- [FoutputToObservable](/api/core/rxjs-interop/outputToObservable)
- [FpendingUntilEvent](/api/core/rxjs-interop/pendingUntilEvent) DEV
- [FrxResource](/api/core/rxjs-interop/rxResource)
- [IRxResourceOptions](/api/core/rxjs-interop/RxResourceOptions)
- [FtakeUntilDestroyed](/api/core/rxjs-interop/takeUntilDestroyed)
- [FtoObservable](/api/core/rxjs-interop/toObservable)
- [IToObservableOptions](/api/core/rxjs-interop/ToObservableOptions)
- [FtoSignal](/api/core/rxjs-interop/toSignal)
- [IToSignalOptions](/api/core/rxjs-interop/ToSignalOptions)

* [CComponentFixture](/api/core/testing/ComponentFixture)
* [KComponentFixtureAutoDetect](/api/core/testing/ComponentFixtureAutoDetect)
* [KComponentFixtureNoNgZone](/api/core/testing/ComponentFixtureNoNgZone)
* [EDeferBlockBehavior](/api/core/testing/DeferBlockBehavior)
* [CDeferBlockFixture](/api/core/testing/DeferBlockFixture)
* [EDeferBlockState](/api/core/testing/DeferBlockState)
* [CDirectiveFixture](/api/core/testing/DirectiveFixture)
* [FdiscardPeriodicTasks](/api/core/testing/discardPeriodicTasks)
* [FfakeAsync](/api/core/testing/fakeAsync)
* [Fflush](/api/core/testing/flush)
* [FflushMicrotasks](/api/core/testing/flushMicrotasks)
* [FgetTestBed](/api/core/testing/getTestBed)
* [Finject](/api/core/testing/inject)
* [CInjectSetupWrapper](/api/core/testing/InjectSetupWrapper)
* [TMetadataOverride](/api/core/testing/MetadataOverride)
* [IModuleTeardownOptions](/api/core/testing/ModuleTeardownOptions)
* [FresetFakeAsyncZone](/api/core/testing/resetFakeAsyncZone)
* [ITestBed](/api/core/testing/TestBed)
* [ITestBedStatic](/api/core/testing/TestBedStatic)
* [ITestComponentOptions](/api/core/testing/TestComponentOptions)
* [CTestComponentRenderer](/api/core/testing/TestComponentRenderer)
* [ITestDirectiveOptions](/api/core/testing/TestDirectiveOptions)
* [ITestEnvironmentOptions](/api/core/testing/TestEnvironmentOptions)
* [ITestModuleMetadata](/api/core/testing/TestModuleMetadata)
* [Ftick](/api/core/testing/tick)
* [FwaitForAsync](/api/core/testing/waitForAsync)
* [FwithModule](/api/core/testing/withModule)

- [FcreateCustomElement](/api/elements/createCustomElement)
- [CNgElement](/api/elements/NgElement)
- [INgElementConfig](/api/elements/NgElementConfig)
- [INgElementConstructor](/api/elements/NgElementConstructor)
- [INgElementStrategy](/api/elements/NgElementStrategy)
- [INgElementStrategyEvent](/api/elements/NgElementStrategyEvent)
- [INgElementStrategyFactory](/api/elements/NgElementStrategyFactory)
- [KVERSION](/api/elements/VERSION)
- [TWithProperties](/api/elements/WithProperties)

* [CAbstractControl](/api/forms/AbstractControl)
* [CAbstractControlDirective](/api/forms/AbstractControlDirective)
* [IAbstractControlOptions](/api/forms/AbstractControlOptions)
* [DAbstractFormDirective](/api/forms/AbstractFormDirective)
* [DAbstractFormGroupDirective](/api/forms/AbstractFormGroupDirective)
* [IAsyncValidator](/api/forms/AsyncValidator)
* [IAsyncValidatorFn](/api/forms/AsyncValidatorFn)
* [DCheckboxControlValueAccessor](/api/forms/CheckboxControlValueAccessor)
* [DCheckboxRequiredValidator](/api/forms/CheckboxRequiredValidator)
* [KCOMPOSITION\_BUFFER\_MODE](/api/forms/COMPOSITION_BUFFER_MODE)
* [TControlConfig](/api/forms/ControlConfig)
* [CControlContainer](/api/forms/ControlContainer)
* [CControlEvent](/api/forms/ControlEvent)
* [IControlValueAccessor](/api/forms/ControlValueAccessor)
* [DDefaultValueAccessor](/api/forms/DefaultValueAccessor)
* [DEmailValidator](/api/forms/EmailValidator)
* [IForm](/api/forms/Form)
* [CFormArray](/api/forms/FormArray)
* [DFormArrayDirective](/api/forms/FormArrayDirective)
* [DFormArrayName](/api/forms/FormArrayName)
* [CFormBuilder](/api/forms/FormBuilder)
* [IFormControl](/api/forms/FormControl)
* [DFormControlDirective](/api/forms/FormControlDirective)
* [DFormControlName](/api/forms/FormControlName)
* [IFormControlOptions](/api/forms/FormControlOptions)
* [IFormControlState](/api/forms/FormControlState)
* [TFormControlStatus](/api/forms/FormControlStatus)
* [CFormGroup](/api/forms/FormGroup)
* [DFormGroupDirective](/api/forms/FormGroupDirective)
* [DFormGroupName](/api/forms/FormGroupName)
* [CFormRecord](/api/forms/FormRecord)
* [CFormResetEvent](/api/forms/FormResetEvent)
* [MFormsModule](/api/forms/FormsModule)
* [CFormSubmittedEvent](/api/forms/FormSubmittedEvent)
* [KisFormArray](/api/forms/isFormArray)
* [KisFormControl](/api/forms/isFormControl)
* [KisFormGroup](/api/forms/isFormGroup)
* [KisFormRecord](/api/forms/isFormRecord)
* [DMaxLengthValidator](/api/forms/MaxLengthValidator)
* [DMaxValidator](/api/forms/MaxValidator)
* [DMinLengthValidator](/api/forms/MinLengthValidator)
* [DMinValidator](/api/forms/MinValidator)
* [KNG\_ASYNC\_VALIDATORS](/api/forms/NG_ASYNC_VALIDATORS)
* [KNG\_VALIDATORS](/api/forms/NG_VALIDATORS)
* [KNG\_VALUE\_ACCESSOR](/api/forms/NG_VALUE_ACCESSOR)
* [CNgControl](/api/forms/NgControl)
* [DNgControlStatus](/api/forms/NgControlStatus)
* [DNgControlStatusGroup](/api/forms/NgControlStatusGroup)
* [DNgForm](/api/forms/NgForm)
* [DNgModel](/api/forms/NgModel)
* [DNgModelGroup](/api/forms/NgModelGroup)
* [DNgSelectOption](/api/forms/NgSelectOption)
* [CNonNullableFormBuilder](/api/forms/NonNullableFormBuilder)
* [DNumberValueAccessor](/api/forms/NumberValueAccessor)
* [DPatternValidator](/api/forms/PatternValidator)
* [CPristineChangeEvent](/api/forms/PristineChangeEvent)
* [DRadioControlValueAccessor](/api/forms/RadioControlValueAccessor)
* [DRangeValueAccessor](/api/forms/RangeValueAccessor)
* [MReactiveFormsModule](/api/forms/ReactiveFormsModule)
* [DRequiredValidator](/api/forms/RequiredValidator)
* [DSelectControlValueAccessor](/api/forms/SelectControlValueAccessor)
* [DSelectMultipleControlValueAccessor](/api/forms/SelectMultipleControlValueAccessor)
* [TSetDisabledStateOption](/api/forms/SetDisabledStateOption)
* [CStatusChangeEvent](/api/forms/StatusChangeEvent)
* [CTouchedChangeEvent](/api/forms/TouchedChangeEvent)
* [TUntypedFormArray](/api/forms/UntypedFormArray)
* [CUntypedFormBuilder](/api/forms/UntypedFormBuilder)
* [TUntypedFormControl](/api/forms/UntypedFormControl)
* [TUntypedFormGroup](/api/forms/UntypedFormGroup)
* [TValidationErrors](/api/forms/ValidationErrors)
* [IValidator](/api/forms/Validator)
* [IValidatorFn](/api/forms/ValidatorFn)
* [CValidators](/api/forms/Validators)
* [CValueChangeEvent](/api/forms/ValueChangeEvent)
* [KVERSION](/api/forms/VERSION)

- [Fapply](/api/forms/signals/apply)
- [FapplyEach](/api/forms/signals/applyEach)
- [FapplyWhen](/api/forms/signals/applyWhen)
- [FapplyWhenValue](/api/forms/signals/applyWhenValue)
- [TAsyncValidationResult](/api/forms/signals/AsyncValidationResult)
- [IAsyncValidatorOptions](/api/forms/signals/AsyncValidatorOptions)
- [CBaseNgValidationError](/api/forms/signals/BaseNgValidationError)
- [IChildFieldContext](/api/forms/signals/ChildFieldContext)
- [TCompatFieldState](/api/forms/signals/CompatFieldState)
- [TCompatSchemaPath](/api/forms/signals/CompatSchemaPath)
- [FcreateLimitSelectionKey](/api/forms/signals/createLimitSelectionKey)
- [FcreateManagedMetadataKey](/api/forms/signals/createManagedMetadataKey)
- [FcreateMetadataKey](/api/forms/signals/createMetadataKey)
- [Fdebounce](/api/forms/signals/debounce)
- [TDebouncer](/api/forms/signals/Debouncer)
- [Fdisabled](/api/forms/signals/disabled)
- [IDisabledReason](/api/forms/signals/DisabledReason)
- [Femail](/api/forms/signals/email)
- [FemailError](/api/forms/signals/emailError)
- [CEmailValidationError](/api/forms/signals/EmailValidationError)
- [TField](/api/forms/signals/Field)
- [TFieldContext](/api/forms/signals/FieldContext)
- [IFieldState](/api/forms/signals/FieldState)
- [TFieldStateByMode](/api/forms/signals/FieldStateByMode)
- [TFieldTree](/api/forms/signals/FieldTree)
- [TFieldValidator](/api/forms/signals/FieldValidator)
- [Fform](/api/forms/signals/form)
- [KFORM\_FIELD](/api/forms/signals/FORM_FIELD)
- [IFormCheckboxControl](/api/forms/signals/FormCheckboxControl)
- [DFormField](/api/forms/signals/FormField)
- [IFormFieldBinding](/api/forms/signals/FormFieldBinding)
- [IFormFieldBindingOptions](/api/forms/signals/FormFieldBindingOptions)
- [IFormOptions](/api/forms/signals/FormOptions)
- [DFormRoot](/api/forms/signals/FormRoot)
- [IFormSubmitOptions](/api/forms/signals/FormSubmitOptions)
- [IFormUiControl](/api/forms/signals/FormUiControl)
- [IFormValueControl](/api/forms/signals/FormValueControl)
- [Fhidden](/api/forms/signals/hidden)
- [IHttpValidatorOptions](/api/forms/signals/HttpValidatorOptions)
- [TIgnoreUnknownProperties](/api/forms/signals/IgnoreUnknownProperties)
- [KIS\_ASYNC\_VALIDATION\_RESOURCE](/api/forms/signals/IS_ASYNC_VALIDATION_RESOURCE)
- [FisFieldTree](/api/forms/signals/isFieldTree)
- [IItemFieldContext](/api/forms/signals/ItemFieldContext)
- [TItemType](/api/forms/signals/ItemType)
- [TLimitKey](/api/forms/signals/LimitKey)
- [TLimitSelectionKey](/api/forms/signals/LimitSelectionKey)
- [TLogicFn](/api/forms/signals/LogicFn)
- [TMapToErrorsFn](/api/forms/signals/MapToErrorsFn)
- [IMarkAsTouchedOptions](/api/forms/signals/MarkAsTouchedOptions)
- [Fmax](/api/forms/signals/max)
- [KMAX](/api/forms/signals/MAX)
- [KMAX\_DATE](/api/forms/signals/MAX_DATE)
- [KMAX\_LENGTH](/api/forms/signals/MAX_LENGTH)
- [KMAX\_NUMBER](/api/forms/signals/MAX_NUMBER)
- [FmaxDate](/api/forms/signals/maxDate)
- [FmaxDateError](/api/forms/signals/maxDateError)
- [CMaxDateValidationError](/api/forms/signals/MaxDateValidationError)
- [FmaxError](/api/forms/signals/maxError)
- [FmaxLength](/api/forms/signals/maxLength)
- [FmaxLengthError](/api/forms/signals/maxLengthError)
- [CMaxLengthValidationError](/api/forms/signals/MaxLengthValidationError)
- [CMaxValidationError](/api/forms/signals/MaxValidationError)
- [TMaybeFieldTree](/api/forms/signals/MaybeFieldTree)
- [TMaybeSchemaPathTree](/api/forms/signals/MaybeSchemaPathTree)
- [Fmetadata](/api/forms/signals/metadata)
- [CMetadataKey](/api/forms/signals/MetadataKey)
- [IMetadataReducer](/api/forms/signals/MetadataReducer)
- [TMetadataSetterType](/api/forms/signals/MetadataSetterType)
- [Fmin](/api/forms/signals/min)
- [KMIN](/api/forms/signals/MIN)
- [KMIN\_DATE](/api/forms/signals/MIN_DATE)
- [KMIN\_LENGTH](/api/forms/signals/MIN_LENGTH)
- [KMIN\_NUMBER](/api/forms/signals/MIN_NUMBER)
- [FminDate](/api/forms/signals/minDate)
- [FminDateError](/api/forms/signals/minDateError)
- [CMinDateValidationError](/api/forms/signals/MinDateValidationError)
- [FminError](/api/forms/signals/minError)
- [FminLength](/api/forms/signals/minLength)
- [FminLengthError](/api/forms/signals/minLengthError)
- [CMinLengthValidationError](/api/forms/signals/MinLengthValidationError)
- [CMinValidationError](/api/forms/signals/MinValidationError)
- [CNativeInputParseError](/api/forms/signals/NativeInputParseError)
- [KNgValidationError](/api/forms/signals/NgValidationError)
- [TOneOrMany](/api/forms/signals/OneOrMany)
- [IParseResult](/api/forms/signals/ParseResult)
- [TPathKind](/api/forms/signals/PathKind)
- [Fpattern](/api/forms/signals/pattern)
- [KPATTERN](/api/forms/signals/PATTERN)
- [FpatternError](/api/forms/signals/patternError)
- [CPatternValidationError](/api/forms/signals/PatternValidationError)
- [FprovideExperimentalWebMcpForms](/api/forms/signals/provideExperimentalWebMcpForms) EXP
- [FprovideSignalFormsConfig](/api/forms/signals/provideSignalFormsConfig)
- [Freadonly](/api/forms/signals/readonly)
- [IReadonlyArrayLike](/api/forms/signals/ReadonlyArrayLike)
- [TReadonlyCompatFieldState](/api/forms/signals/ReadonlyCompatFieldState)
- [IReadonlyFieldState](/api/forms/signals/ReadonlyFieldState)
- [TReadonlyFieldTree](/api/forms/signals/ReadonlyFieldTree)
- [TRemoveStringIndexUnknownKey](/api/forms/signals/RemoveStringIndexUnknownKey)
- [Frequired](/api/forms/signals/required)
- [KREQUIRED](/api/forms/signals/REQUIRED)
- [FrequiredError](/api/forms/signals/requiredError)
- [CRequiredValidationError](/api/forms/signals/RequiredValidationError)
- [IRootFieldContext](/api/forms/signals/RootFieldContext)
- [Fschema](/api/forms/signals/schema)
- [TSchema](/api/forms/signals/Schema)
- [TSchemaFn](/api/forms/signals/SchemaFn)
- [TSchemaOrSchemaFn](/api/forms/signals/SchemaOrSchemaFn)
- [TSchemaPath](/api/forms/signals/SchemaPath)
- [TSchemaPathRules](/api/forms/signals/SchemaPathRules)
- [TSchemaPathTree](/api/forms/signals/SchemaPathTree)
- [ISignalFormsConfig](/api/forms/signals/SignalFormsConfig)
- [FstandardSchemaError](/api/forms/signals/standardSchemaError)
- [CStandardSchemaValidationError](/api/forms/signals/StandardSchemaValidationError)
- [TSubfields](/api/forms/signals/Subfields)
- [Fsubmit](/api/forms/signals/submit)
- [FtransformedValue](/api/forms/signals/transformedValue)
- [ITransformedValueOptions](/api/forms/signals/TransformedValueOptions)
- [ITransformedValueSignal](/api/forms/signals/TransformedValueSignal)
- [TTreeValidationResult](/api/forms/signals/TreeValidationResult)
- [TTreeValidator](/api/forms/signals/TreeValidator)
- [Fvalidate](/api/forms/signals/validate)
- [FvalidateAsync](/api/forms/signals/validateAsync)
- [FvalidateHttp](/api/forms/signals/validateHttp)
- [FvalidateStandardSchema](/api/forms/signals/validateStandardSchema)
- [FvalidateTree](/api/forms/signals/validateTree)
- [IValidationError](/api/forms/signals/ValidationError)
- [IValidationErrorOptions](/api/forms/signals/ValidationErrorOptions)
- [TValidationResult](/api/forms/signals/ValidationResult)
- [TValidationSuccess](/api/forms/signals/ValidationSuccess)
- [TValidator](/api/forms/signals/Validator)
- [TWithFieldTree](/api/forms/signals/WithFieldTree)
- [TWithOptionalFieldTree](/api/forms/signals/WithOptionalFieldTree)
- [TWithoutFieldTree](/api/forms/signals/WithoutFieldTree)

* [FcompatForm](/api/forms/signals/compat/compatForm)
* [TCompatFormOptions](/api/forms/signals/compat/CompatFormOptions)
* [FextractValue](/api/forms/signals/compat/extractValue)
* [KNG\_STATUS\_CLASSES](/api/forms/signals/compat/NG_STATUS_CLASSES)
* [CSignalFormControl](/api/forms/signals/compat/SignalFormControl)

- [FclearTranslations](/api/localize/clearTranslations)
- [FloadTranslations](/api/localize/loadTranslations)
- [TMessageId](/api/localize/MessageId)
- [TTargetMessage](/api/localize/TargetMessage)

* [K$localize](/api/localize/init/$localize)

- [FbootstrapApplication](/api/platform-browser/bootstrapApplication)
- [IBootstrapContext](/api/platform-browser/BootstrapContext)
- [MBrowserModule](/api/platform-browser/BrowserModule)
- [CBy](/api/platform-browser/By)
- [FcreateApplication](/api/platform-browser/createApplication)
- [CCssVarNamespacer](/api/platform-browser/CssVarNamespacer)
- [FdisableDebugTools](/api/platform-browser/disableDebugTools)
- [CDomSanitizer](/api/platform-browser/DomSanitizer)
- [FenableDebugTools](/api/platform-browser/enableDebugTools)
- [KEVENT\_MANAGER\_PLUGINS](/api/platform-browser/EVENT_MANAGER_PLUGINS)
- [CEventManager](/api/platform-browser/EventManager)
- [CEventManagerPlugin](/api/platform-browser/EventManagerPlugin)
- [IHydrationFeature](/api/platform-browser/HydrationFeature)
- [EHydrationFeatureKind](/api/platform-browser/HydrationFeatureKind)
- [CMeta](/api/platform-browser/Meta)
- [TMetaDefinition](/api/platform-browser/MetaDefinition)
- [KplatformBrowser](/api/platform-browser/platformBrowser)
- [FprovideClientHydration](/api/platform-browser/provideClientHydration)
- [FprovideCssVarNamespacing](/api/platform-browser/provideCssVarNamespacing)
- [FprovideProtractorTestingSupport](/api/platform-browser/provideProtractorTestingSupport)
- [KREMOVE\_STYLES\_ON\_COMPONENT\_DESTROY](/api/platform-browser/REMOVE_STYLES_ON_COMPONENT_DESTROY)
- [ISafeHtml](/api/platform-browser/SafeHtml)
- [ISafeResourceUrl](/api/platform-browser/SafeResourceUrl)
- [ISafeScript](/api/platform-browser/SafeScript)
- [ISafeStyle](/api/platform-browser/SafeStyle)
- [ISafeUrl](/api/platform-browser/SafeUrl)
- [ISafeValue](/api/platform-browser/SafeValue)
- [CTitle](/api/platform-browser/Title)
- [KVERSION](/api/platform-browser/VERSION)
- [FwithEventReplay](/api/platform-browser/withEventReplay)
- [FwithHttpTransferCacheOptions](/api/platform-browser/withHttpTransferCacheOptions)
- [FwithI18nSupport](/api/platform-browser/withI18nSupport)
- [FwithNoHttpTransferCache](/api/platform-browser/withNoHttpTransferCache)
- [FwithNoIncrementalHydration](/api/platform-browser/withNoIncrementalHydration)

* [KVERSION](/api/platform-browser-dynamic/VERSION)

- [KANIMATION\_MODULE\_TYPE](/api/platform-browser/animations/ANIMATION_MODULE_TYPE)

* [MBrowserTestingModule](/api/platform-browser/testing/BrowserTestingModule)
* [KplatformBrowserTesting](/api/platform-browser/testing/platformBrowserTesting)

- [KBEFORE\_APP\_SERIALIZED](/api/platform-server/BEFORE_APP_SERIALIZED)
- [KINITIAL\_CONFIG](/api/platform-server/INITIAL_CONFIG)
- [IPlatformConfig](/api/platform-server/PlatformConfig)
- [FplatformServer](/api/platform-server/platformServer)
- [CPlatformState](/api/platform-server/PlatformState)
- [FprovideServerRendering](/api/platform-server/provideServerRendering)
- [FrenderApplication](/api/platform-server/renderApplication)
- [FrenderModule](/api/platform-server/renderModule)
- [ERuntimeErrorCode](/api/platform-server/RuntimeErrorCode)
- [MServerModule](/api/platform-server/ServerModule)
- [KVERSION](/api/platform-server/VERSION)

* [CActivatedRoute](/api/router/ActivatedRoute)
* [CActivatedRouteSnapshot](/api/router/ActivatedRouteSnapshot)
* [CActivationEnd](/api/router/ActivationEnd)
* [CActivationStart](/api/router/ActivationStart)
* [TAutoCleanupInjectorsFeature](/api/router/AutoCleanupInjectorsFeature)
* [CBaseRouteReuseStrategy](/api/router/BaseRouteReuseStrategy)
* [ICanActivate](/api/router/CanActivate)
* [ICanActivateChild](/api/router/CanActivateChild)
* [TCanActivateChildFn](/api/router/CanActivateChildFn)
* [TCanActivateFn](/api/router/CanActivateFn)
* [ICanDeactivate](/api/router/CanDeactivate)
* [TCanDeactivateFn](/api/router/CanDeactivateFn)
* [ICanMatch](/api/router/CanMatch)
* [TCanMatchFn](/api/router/CanMatchFn)
* [CChildActivationEnd](/api/router/ChildActivationEnd)
* [CChildActivationStart](/api/router/ChildActivationStart)
* [CChildrenOutletContexts](/api/router/ChildrenOutletContexts)
* [TComponentInputBindingFeature](/api/router/ComponentInputBindingFeature)
* [IComponentInputBindingOptions](/api/router/ComponentInputBindingOptions)
* [FcontainsTree](/api/router/containsTree)
* [FconvertToParamMap](/api/router/convertToParamMap)
* [FcreateUrlTreeFromSnapshot](/api/router/createUrlTreeFromSnapshot)
* [TData](/api/router/Data)
* [TDebugTracingFeature](/api/router/DebugTracingFeature)
* [IDefaultExport](/api/router/DefaultExport)
* [CDefaultTitleStrategy](/api/router/DefaultTitleStrategy)
* [FdefaultUrlMatcher](/api/router/defaultUrlMatcher)
* [CDefaultUrlSerializer](/api/router/DefaultUrlSerializer)
* [FdestroyDetachedRouteHandle](/api/router/destroyDetachedRouteHandle)
* [TDetachedRouteHandle](/api/router/DetachedRouteHandle)
* [TDisabledInitialNavigationFeature](/api/router/DisabledInitialNavigationFeature)
* [TEnabledBlockingInitialNavigationFeature](/api/router/EnabledBlockingInitialNavigationFeature)
* [TEvent](/api/router/Event)
* [EEventType](/api/router/EventType)
* [IExtraOptions](/api/router/ExtraOptions)
* [TGuardResult](/api/router/GuardResult)
* [CGuardsCheckEnd](/api/router/GuardsCheckEnd)
* [CGuardsCheckStart](/api/router/GuardsCheckStart)
* [TInitialNavigation](/api/router/InitialNavigation)
* [TInitialNavigationFeature](/api/router/InitialNavigationFeature)
* [TInMemoryScrollingFeature](/api/router/InMemoryScrollingFeature)
* [IInMemoryScrollingOptions](/api/router/InMemoryScrollingOptions)
* [FisActive](/api/router/isActive)
* [IIsActiveMatchOptions](/api/router/IsActiveMatchOptions)
* [TLoadChildren](/api/router/LoadChildren)
* [TLoadChildrenCallback](/api/router/LoadChildrenCallback)
* [FmapToCanActivate](/api/router/mapToCanActivate)
* [FmapToCanActivateChild](/api/router/mapToCanActivateChild)
* [FmapToCanDeactivate](/api/router/mapToCanDeactivate)
* [FmapToCanMatch](/api/router/mapToCanMatch)
* [FmapToResolve](/api/router/mapToResolve)
* [TMaybeAsync](/api/router/MaybeAsync)
* [INavigation](/api/router/Navigation)
* [INavigationBehaviorOptions](/api/router/NavigationBehaviorOptions)
* [CNavigationCancel](/api/router/NavigationCancel)
* [ENavigationCancellationCode](/api/router/NavigationCancellationCode)
* [CNavigationEnd](/api/router/NavigationEnd)
* [CNavigationError](/api/router/NavigationError)
* [TNavigationErrorHandlerFeature](/api/router/NavigationErrorHandlerFeature)
* [INavigationExtras](/api/router/NavigationExtras)
* [CNavigationSkipped](/api/router/NavigationSkipped)
* [ENavigationSkippedCode](/api/router/NavigationSkippedCode)
* [CNavigationStart](/api/router/NavigationStart)
* [FnonBlocking](/api/router/nonBlocking) DEV
* [CNoPreloading](/api/router/NoPreloading)
* [TOnSameUrlNavigation](/api/router/OnSameUrlNavigation)
* [COutletContext](/api/router/OutletContext)
* [IParamMap](/api/router/ParamMap)
* [TParams](/api/router/Params)
* [TPartialMatchRouteSnapshot](/api/router/PartialMatchRouteSnapshot)
* [CPreloadAllModules](/api/router/PreloadAllModules)
* [TPreloadingFeature](/api/router/PreloadingFeature)
* [CPreloadingStrategy](/api/router/PreloadingStrategy)
* [KPRIMARY\_OUTLET](/api/router/PRIMARY_OUTLET)
* [FprovideRouter](/api/router/provideRouter)
* [TQueryParamsHandling](/api/router/QueryParamsHandling)
* [CRedirectCommand](/api/router/RedirectCommand)
* [TRedirectFunction](/api/router/RedirectFunction)
* [IResolve](/api/router/Resolve)
* [TResolveData](/api/router/ResolveData)
* [CResolveEnd](/api/router/ResolveEnd)
* [TResolveFn](/api/router/ResolveFn)
* [CResolveStart](/api/router/ResolveStart)
* [IResourceContext](/api/router/ResourceContext) DEV
* [TResourceResult](/api/router/ResourceResult) DEV
* [IRoute](/api/router/Route)
* [CRouteConfigLoadEnd](/api/router/RouteConfigLoadEnd)
* [CRouteConfigLoadStart](/api/router/RouteConfigLoadStart)
* [CRouter](/api/router/Router)
* [KROUTER\_CONFIGURATION](/api/router/ROUTER_CONFIGURATION)
* [KROUTER\_INITIALIZER](/api/router/ROUTER_INITIALIZER)
* [KROUTER\_OUTLET\_DATA](/api/router/ROUTER_OUTLET_DATA)
* [IRouterConfigOptions](/api/router/RouterConfigOptions)
* [TRouterConfigurationFeature](/api/router/RouterConfigurationFeature)
* [CRouteReuseStrategy](/api/router/RouteReuseStrategy)
* [CRouterEvent](/api/router/RouterEvent)
* [IRouterFeature](/api/router/RouterFeature)
* [TRouterFeatures](/api/router/RouterFeatures)
* [TRouterHashLocationFeature](/api/router/RouterHashLocationFeature)
* [DRouterLink](/api/router/RouterLink)
* [DRouterLinkActive](/api/router/RouterLinkActive)
* [DRouterLinkWithHref](/api/router/RouterLinkWithHref)
* [MRouterModule](/api/router/RouterModule)
* [DRouterOutlet](/api/router/RouterOutlet)
* [IRouterOutletContract](/api/router/RouterOutletContract)
* [CRouterPreloader](/api/router/RouterPreloader)
* [TRouterResourcesFeature](/api/router/RouterResourcesFeature) DEV
* [CRouterState](/api/router/RouterState)
* [CRouterStateSnapshot](/api/router/RouterStateSnapshot)
* [TRoutes](/api/router/Routes)
* [KROUTES](/api/router/ROUTES)
* [CRoutesRecognized](/api/router/RoutesRecognized)
* [TRunGuardsAndResolvers](/api/router/RunGuardsAndResolvers)
* [CScroll](/api/router/Scroll)
* [CTitleStrategy](/api/router/TitleStrategy)
* [IUrlCreationOptions](/api/router/UrlCreationOptions)
* [CUrlHandlingStrategy](/api/router/UrlHandlingStrategy)
* [TUrlMatcher](/api/router/UrlMatcher)
* [TUrlMatchResult](/api/router/UrlMatchResult)
* [CUrlSegment](/api/router/UrlSegment)
* [CUrlSegmentGroup](/api/router/UrlSegmentGroup)
* [CUrlSerializer](/api/router/UrlSerializer)
* [CUrlTree](/api/router/UrlTree)
* [KVERSION](/api/router/VERSION)
* [IViewTransitionInfo](/api/router/ViewTransitionInfo) DEV
* [TViewTransitionsFeature](/api/router/ViewTransitionsFeature)
* [IViewTransitionsFeatureOptions](/api/router/ViewTransitionsFeatureOptions) DEV
* [FwithAutoCleanupInjectors](/api/router/withAutoCleanupInjectors)
* [FwithComponentInputBinding](/api/router/withComponentInputBinding)
* [FwithDebugTracing](/api/router/withDebugTracing)
* [FwithDisabledInitialNavigation](/api/router/withDisabledInitialNavigation)
* [FwithEnabledBlockingInitialNavigation](/api/router/withEnabledBlockingInitialNavigation)
* [FwithExperimentalPlatformNavigation](/api/router/withExperimentalPlatformNavigation) EXP
* [FwithHashLocation](/api/router/withHashLocation)
* [FwithInMemoryScrolling](/api/router/withInMemoryScrolling)
* [FwithNavigationErrorHandler](/api/router/withNavigationErrorHandler)
* [FwithPreloading](/api/router/withPreloading)
* [FwithRouterConfig](/api/router/withRouterConfig)
* [FwithRouterResources](/api/router/withRouterResources) DEV
* [FwithViewTransitions](/api/router/withViewTransitions) DEV

- [CRouterTestingHarness](/api/router/testing/RouterTestingHarness)

* [FlocationSyncBootstrapListener](/api/router/upgrade/locationSyncBootstrapListener)
* [KRouterUpgradeInitializer](/api/router/upgrade/RouterUpgradeInitializer)
* [FsetUpLocationSync](/api/router/upgrade/setUpLocationSync)

- [INoNewVersionDetectedEvent](/api/service-worker/NoNewVersionDetectedEvent)
- [FprovideServiceWorker](/api/service-worker/provideServiceWorker)
- [MServiceWorkerModule](/api/service-worker/ServiceWorkerModule)
- [CSwPush](/api/service-worker/SwPush)
- [CSwRegistrationOptions](/api/service-worker/SwRegistrationOptions)
- [CSwUpdate](/api/service-worker/SwUpdate)
- [IUnrecoverableStateEvent](/api/service-worker/UnrecoverableStateEvent)
- [IVersionDetectedEvent](/api/service-worker/VersionDetectedEvent)
- [TVersionEvent](/api/service-worker/VersionEvent)
- [IVersionInstallationFailedEvent](/api/service-worker/VersionInstallationFailedEvent)
- [IVersionReadyEvent](/api/service-worker/VersionReadyEvent)

* [CAngularAppEngine](/api/ssr/AngularAppEngine)
* [IAngularAppEngineOptions](/api/ssr/AngularAppEngineOptions)
* [FcreateRequestHandler](/api/ssr/createRequestHandler)
* [KIS\_DISCOVERING\_ROUTES](/api/ssr/IS_DISCOVERING_ROUTES)
* [EPrerenderFallback](/api/ssr/PrerenderFallback)
* [FprovideServerRendering](/api/ssr/provideServerRendering)
* [ERenderMode](/api/ssr/RenderMode)
* [TRequestHandlerFunction](/api/ssr/RequestHandlerFunction)
* [IServerRenderingOptions](/api/ssr/ServerRenderingOptions)
* [TServerRoute](/api/ssr/ServerRoute)
* [IServerRouteClient](/api/ssr/ServerRouteClient)
* [IServerRouteCommon](/api/ssr/ServerRouteCommon)
* [IServerRoutePrerender](/api/ssr/ServerRoutePrerender)
* [IServerRoutePrerenderWithParams](/api/ssr/ServerRoutePrerenderWithParams)
* [IServerRouteServer](/api/ssr/ServerRouteServer)
* [FwithAppShell](/api/ssr/withAppShell)
* [FwithRoutes](/api/ssr/withRoutes)

- [CAngularNodeAppEngine](/api/ssr/node/AngularNodeAppEngine)
- [IAngularNodeAppEngineOptions](/api/ssr/node/AngularNodeAppEngineOptions)
- [FcreateNodeRequestHandler](/api/ssr/node/createNodeRequestHandler)
- [FcreateWebRequestFromNodeRequest](/api/ssr/node/createWebRequestFromNodeRequest)
- [FisMainModule](/api/ssr/node/isMainModule)
- [TNodeRequestHandlerFunction](/api/ssr/node/NodeRequestHandlerFunction)
- [FwriteResponseToNodeResponse](/api/ssr/node/writeResponseToNodeResponse)

* [KVERSION](/api/upgrade/VERSION)

- [FdowngradeComponent](/api/upgrade/static/downgradeComponent)
- [FdowngradeInjectable](/api/upgrade/static/downgradeInjectable)
- [FdowngradeModule](/api/upgrade/static/downgradeModule)
- [FgetAngularJSGlobal](/api/upgrade/static/getAngularJSGlobal)
- [FsetAngularJSGlobal](/api/upgrade/static/setAngularJSGlobal)
- [DUpgradeComponent](/api/upgrade/static/UpgradeComponent)
- [MUpgradeModule](/api/upgrade/static/UpgradeModule)
- [KVERSION](/api/upgrade/static/VERSION)

* [FcreateAngularJSTestingModule](/api/upgrade/static/testing/createAngularJSTestingModule)
* [FcreateAngularTestingModule](/api/upgrade/static/testing/createAngularTestingModule)

- [FapplyChanges](/api/core/globals/applyChanges)
- [TDirectiveDebugMetadata](/api/core/globals/DirectiveDebugMetadata)
- [FenableProfiling](/api/core/globals/enableProfiling)
- [FgetComponent](/api/core/globals/getComponent)
- [FgetContext](/api/core/globals/getContext)
- [FgetDirectiveMetadata](/api/core/globals/getDirectiveMetadata)
- [FgetDirectives](/api/core/globals/getDirectives)
- [FgetHostElement](/api/core/globals/getHostElement)
- [FgetInjector](/api/core/globals/getInjector)
- [FgetListeners](/api/core/globals/getListeners)
- [FgetOwningComponent](/api/core/globals/getOwningComponent)
- [FgetRootComponents](/api/core/globals/getRootComponents)
- [IListener](/api/core/globals/Listener)

----
url: https://angular.dev/api/core/provideStabilityDebugging
----

```
function provideStabilityDebugging(): EnvironmentProviders;
```

@returns`EnvironmentProviders`

## [Description](#description)

Provides an application initializer that will log information about what tasks are keeping the application from stabilizing if the application does not stabilize within 9 seconds.

The logged information includes the stack of the tasks preventing stability. This stack can be traced back to the source in the application code.

If you are using Zone.js, it is recommended that you also temporarily import "zone.js/plugins/task-tracking". This Zone.js plugin provides additional information about which macrotasks are scheduled in the Angular Zone and keeping the Zone from stabilizing.

## [Usage Notes](#usage-notes)

```
import 'zone.js/plugins/task-tracking';

bootstrapApplication(AppComponent, {providers: [provideStabilityDebugging()]});
```

**IMPORTANT:** Neither the zone.js task tracking plugin nor this utility are removed from production bundles. They are intended for temporary use while debugging stability issues during development, including for optimized production builds.

Jump to details

----
url: https://angular.dev/guide/routing/define-routes
----

Routes serve as the fundamental building blocks for navigation within an Angular app.

## [What are routes?](#what-are-routes)

In Angular, a **route** is an object that defines which component should render for a specific URL path or pattern, as well as additional configuration options about what happens when a user navigates to that URL.

Here is a basic example of a route:

```
import {AdminPage} from './app-admin';

const adminPage = {
  path: 'admin',
  component: AdminPage,
};
```

For this route, when a user visits the `/admin` path, the app will display the `AdminPage` component.

### [Managing routes in your application](#managing-routes-in-your-application)

Most projects define routes in a separate file that contains `routes` in the filename.

A collection of routes looks like this:

```
import {Routes} from '@angular/router';
import {HomePage} from './home-page';
import {AdminPage} from './admin-page';

export const routes: Routes = [
  {
    path: '',
    component: HomePage,
  },
  {
    path: 'admin',
    component: AdminPage,
  },
];
```

**TIP:** If you generated a project with Angular CLI, your routes are defined in `src/app/app.routes.ts`.

### [Adding the router to your application](#adding-the-router-to-your-application)

When bootstrapping an Angular application without the Angular CLI, you can pass a configuration object that includes a `providers` array.

Inside of the `providers` array, you can add the Angular router to your application by adding a [`provideRouter`](/api/router/provideRouter) function call with your routes.

```
import {ApplicationConfig} from '@angular/core';
import {provideRouter} from '@angular/router';

import {routes} from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    // ...
  ],
};
```

## [Route URL Paths](#route-url-paths)

### [Static URL Paths](#static-url-paths)

Static URL Paths refer to routes with predefined paths that don't change based on dynamic parameters. These are routes that match a `path` string exactly and have a fixed outcome.

Examples of this include:

* "/admin"
* "/blog"
* "/settings/account"

### [Define URL Paths with Route Parameters](#define-url-paths-with-route-parameters)

Parameterized URLs allow you to define dynamic paths that allow multiple URLs to the same component while dynamically displaying data based on parameters in the URL.

You can define this type of pattern by adding parameters to your route’s `path` string and prefixing each parameter with the colon (`:`) character.

**IMPORTANT:** Parameters are distinct from information in the URL's [query string](https://en.wikipedia.org/wiki/Query_string). Learn more about [query parameters in Angular in this guide](/guide/routing/read-route-state#query-parameters).

The following example displays a user profile component based on the user id passed in through the URL.

```
import {Routes} from '@angular/router';
import {UserProfile} from './user-profile/user-profile';

const routes: Routes = [{path: 'user/:id', component: UserProfile}];
```

In this example, URLs such as `/user/leeroy` and `/user/jenkins` render the `UserProfile` component. This component can then read the `id` parameter and use it to perform additional work, such as fetching data. See [reading route state guide](/guide/routing/read-route-state) for details on reading route parameters.

Valid route parameter names must start with a letter (a-z, A-Z) and can only contain:

* Letters (a-z, A-Z)
* Numbers (0-9)
* Underscore (\_)
* Hyphen (-)

You can also define paths with multiple parameters:

```
import {Routes} from '@angular/router';
import {UserProfile} from './user-profile';
import {SocialMediaFeed} from './social-media-feed';

const routes: Routes = [
  {path: 'user/:id/:social-media', component: SocialMediaFeed},
  {path: 'user/:id', component: UserProfile},
];
```

With this new path, users can visit `/user/leeroy/youtube` and `/user/leeroy/bluesky` and see respective social media feeds based on the parameter for the user leeroy.

See [Reading route state](/guide/routing/read-route-state) for details on reading route parameters.

### [Wildcards](#wildcards)

When you need to catch all routes for a specific path, the solution is a wildcard route which is defined with the double asterisk (`**`).

A common example is defining a Page Not Found component.

```
import {Home} from './home/home';
import {UserProfile} from './user-profile';
import {NotFound} from './not-found';

const routes: Routes = [
  {path: 'home', component: Home},
  {path: 'user/:id', component: UserProfile},
  {path: '**', component: NotFound},
];
```

In this routes array, the app displays the `NotFound` component when the user visits any path outside of `home` and `user/:id`.

**TIP:** Wildcard routes are typically placed at the end of a routes array.

## [How Angular matches URLs](#how-angular-matches-urls)

When you define routes, the order is important because Angular uses a first-match wins strategy. This means that once Angular matches a URL with a route `path`, it stops checking any further routes. As a result, always put more specific routes before less specific routes.

The following example shows routes defined from most-specific to least specific:

```
const routes: Routes = [
  {path: '', component: Home}, // Empty path
  {path: 'users/new', component: NewUser}, // Static, most specific
  {path: 'users/:id', component: UserDetail}, // Dynamic
  {path: 'users', component: Users}, // Static, less specific
  {path: '**', component: NotFound}, // Wildcard - always last
];
```

If a user visits `/users/new`, Angular router would go through the following steps:

1. Checks `''` - doesn't match
2. Checks `users/new` - matches! Stops here
3. Never reaches `users/:id` even though it could match
4. Never reaches `users`
5. Never reaches `**`

## [Redirects](#redirects)

You can define a route that redirects to another route instead of rendering a component:

```
import {Blog} from './home/blog';

const routes: Routes = [
  {
    path: 'articles',
    redirectTo: '/blog',
  },
  {
    path: 'blog',
    component: Blog,
  },
];
```

If you modify or remove a route, some users may still click on out-of-date links or bookmarks to that route. You can add a redirect to direct those users to an appropriate alternative route instead of a "not found" page.

## [Page titles](#page-titles)

You can associate a **title** with each route. Angular automatically updates the [page title](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title) when a route activates. Always define appropriate page titles for your application, as these titles are necessary to create an accessible experience.

```
import {Routes} from '@angular/router';
import {Home} from './home';
import {About} from './about';
import {Products} from './products';

const routes: Routes = [
  {
    path: '',
    component: Home,
    title: 'Home Page',
  },
  {
    path: 'about',
    component: About,
    title: 'About Us',
  },
];
```

The page `title` property can be set dynamically to a resolver function using [`ResolveFn`](/api/router/ResolveFn).

```
const titleResolver: ResolveFn<string> = (route) => route.queryParams['id'];
const routes: Routes = [
  // ...
  {
    path: 'products',
    component: Products,
    title: titleResolver,
  },
];
```

Route titles can also be set via a service extending the [`TitleStrategy`](/api/router/TitleStrategy) abstract class. By default, Angular uses the [`DefaultTitleStrategy`](/api/router/DefaultTitleStrategy).

### [Using TitleStrategy for page titles](#using-titlestrategy-for-page-titles)

For advanced scenarios where you need centralized control over how the document title is composed, implement a [`TitleStrategy`](/api/router/TitleStrategy).

[`TitleStrategy`](/api/router/TitleStrategy) is a token you can provide to override the default title strategy used by Angular. You can supply a custom [`TitleStrategy`](/api/router/TitleStrategy) to implement conventions such as adding an application suffix, formatting titles from breadcrumbs, or generating titles dynamically from route data.

```
import {inject, Injectable} from '@angular/core';
import {Title} from '@angular/platform-browser';
import {TitleStrategy, RouterStateSnapshot} from '@angular/router';

@Injectable()
export class AppTitleStrategy extends TitleStrategy {
  private readonly title = inject(Title);

  updateTitle(snapshot: RouterStateSnapshot): void {
    // PageTitle is equal to the "Title" of a route if it's set
    // If its not set it will use the "title" given in index.html
    const pageTitle = this.buildTitle(snapshot) || this.title.getTitle();
    this.title.setTitle(`MyAwesomeApp - ${pageTitle}`);
  }
}
```

To use the custom strategy, provide it with the [`TitleStrategy`](/api/router/TitleStrategy) token at the application level:

```
import {provideRouter, TitleStrategy} from '@angular/router';
import {AppTitleStrategy} from './app-title.strategy';

export const appConfig = {
  providers: [provideRouter(routes), {provide: TitleStrategy, useClass: AppTitleStrategy}],
};
```

## [Route-level providers for dependency injection](#route-level-providers-for-dependency-injection)

Each route has a `providers` property that lets you provide dependencies to that route's content via [dependency injection](/guide/di).

Common scenarios where this can be helpful include applications that have different services based on whether the user is an admin or not.

```
export const ROUTES: Route[] = [
  {
    path: 'admin',
    providers: [AdminService, {provide: ADMIN_API_KEY, useValue: '12345'}],
    children: [
      {path: 'users', component: AdminUsers},
      {path: 'teams', component: AdminTeams},
    ],
  },
  // ... other application routes that don't
  //     have access to ADMIN_API_KEY or AdminService.
];
```

In this code sample, the `admin` path contains a protected data property of `ADMIN_API_KEY` that is only available to children within its section. As a result, no other paths will be able to access the data provided via `ADMIN_API_KEY`.

See the [Dependency injection guide](/guide/di) for more information about providers and injection in Angular.

## [Associating data with routes](#associating-data-with-routes)

Route data enables you to attach additional information to routes. You are able to configure how components behave based on this data.

There are two ways to work with route data: static data that remains constant, and dynamic data that can change based on runtime conditions.

### [Static data](#static-data)

You can associate arbitrary static data with a route via the `data` property in order to centralize things like route-specific metadata (e.g., analytics tracking, permissions, etc.):

```
import {Routes} from '@angular/router';
import {Home} from './home';
import {About} from './about';
import {Products} from './products';

const routes: Routes = [
  {
    path: 'about',
    component: About,
    data: {analyticsId: '456'},
  },
  {
    path: '',
    component: Home,
    data: {analyticsId: '123'},
  },
];
```

In this code sample, the home and about page are configured with specific `analyticsId` which would then be used in their respective components for page tracking analytics.

You can read this static data by injecting the [`ActivatedRoute`](/api/router/ActivatedRoute). See [Reading route state](/guide/routing/read-route-state) for details.

### [Dynamic data with resources and resolvers](#dynamic-data-with-resources-and-resolvers)

When you need to fetch data for a route, Angular Router supports reactive route resources as well as data resolvers:

* [Data fetching with resources](/guide/routing/data-fetching-with-resources): Fetch data reactively using Angular Signals [`Resource`](/api/core/Resource) APIs.
* [Route data resolvers](/guide/routing/data-resolvers): Fetch data before route activation using resolver functions.

## [Nested Routes](#nested-routes)

Nested routes, also known as child routes, are a common technique for managing more complex navigation routes where a component has a sub-view that changes based on the URL.

You can add child routes to any route definition with the `children` property:

```
const routes: Routes = [
  {
    path: 'product/:id',
    component: Product,
    children: [
      {
        path: 'info',
        component: ProductInfo,
      },
      {
        path: 'reviews',
        component: ProductReviews,
      },
    ],
  },
];
```

The above example defines a route for a product page that allows a user to change whether the product info or reviews are displayed based on the url.

The `children` property accepts an array of [`Route`](/api/router/Route) objects.

To display child routes, the parent component (`Product` in the example above) includes its own `<router-outlet>`.

```
<!-- Product -->
<article>
  <h1>Product {{ id }}</h1>
  <router-outlet />
</article>
```

After adding child routes to the configuration and adding a `<router-outlet>` to the component, navigation between URLs that match the child routes updates only the nested outlet.

## [Next steps](#next-steps)

----
url: https://angular.dev/api/common/http/HttpInterceptorFn
----

```
    
      type HttpInterceptorFn = (  req: HttpRequest<unknown>,  next: HttpHandlerFn,) => Observable<HttpEvent<unknown>>
    
    
```

## [Description](#description)

An interceptor for HTTP requests made via [`HttpClient`](/api/common/http/HttpClient).

[`HttpInterceptorFn`](/api/common/http/HttpInterceptorFn)s are middleware functions which [`HttpClient`](/api/common/http/HttpClient) calls when a request is made. These functions have the opportunity to modify the outgoing request or any response that comes back, as well as block, redirect, or otherwise change the request or response semantics.

An [`HttpHandlerFn`](/api/common/http/HttpHandlerFn) representing the next interceptor (or the backend which will make a real HTTP request) is provided. Most interceptors will delegate to this function, but that is not required (see [`HttpHandlerFn`](/api/common/http/HttpHandlerFn) for more details).

[`HttpInterceptorFn`](/api/common/http/HttpInterceptorFn)s are executed in an [injection context](guide/di/dependency-injection-context). They have access to [`inject()`](/api/core/inject) via the [`EnvironmentInjector`](/api/core/EnvironmentInjector) from which they were configured.

## [Usage Notes](#usage-notes)

Here is a noop interceptor that passes the request through without modifying it:

```
export const noopInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next:
HttpHandlerFn) => {
  return next(modifiedReq);
};
```

If you want to alter a request, clone it first and modify the clone before passing it to the `next()` handler function.

Here is a basic interceptor that adds a bearer token to the headers

```
export const authenticationInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next:
HttpHandlerFn) => {
   const userToken = 'MY_TOKEN'; const modifiedReq = req.clone({
     headers: req.headers.set('Authorization', `Bearer ${userToken}`),
   });

   return next(modifiedReq);
};
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpErrorResponse
----

```
    
      class HttpErrorResponse extends HttpResponseBase implements Error {}
    
    
```

@paraminit`{ error?: any; headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; redirected?: boolean | undefined; responseType?: ResponseType | undefined; }`

@returns`HttpErrorResponse`

Errors are never okay, even when the status code is in the 2xx success range.

## [Description](#description)

A response that represents an error or failure, either from a non-successful HTTP status, an error while executing the request, or some other failure which occurred during the parsing of the response.

Any error returned on the `Observable` response stream will be wrapped in an [`HttpErrorResponse`](/api/common/http/HttpErrorResponse) to provide additional context about the state of the HTTP layer when the error occurred. The error property will contain either a wrapped Error object or the error response returned from the server.

Jump to details

----
url: https://angular.dev/api/common/upgrade/LocationUpgradeModule
----

```
    
      class LocationUpgradeModule {}
    
    
```

@paramconfig`LocationUpgradeConfig | undefined`

@returns`ModuleWithProviders<LocationUpgradeModule>`

Jump to details

----
url: https://angular.dev/guide/i18n/translation-files
----

After you prepare a component for translation, use the [`extract-i18n`](cli/extract-i18n "ng extract-i18n | CLI | Angular") [Angular CLI](cli "CLI Overview and Command Reference | Angular") command to extract the marked text in the component into a *source language* file.

The marked text includes text marked with `i18n`, attributes marked with `i18n-`*attribute*, and text tagged with [`$localize`](/api/localize/init/$localize) as described in [Prepare component for translation](guide/i18n/prepare "Prepare component for translation | Angular").

Complete the following steps to create and update translation files for your project.

1. [Extract the source language file](guide/i18n/translation-files#extract-the-source-language-file "Extract the source language file - Work with translation files | Angular").
   1. Optionally, change the location, format, and name.

2. Copy the source language file to [create a translation file for each language](guide/i18n/translation-files#create-a-translation-file-for-each-language "Create a translation file for each language - Work with translation files | Angular").

3. [Translate each translation file](guide/i18n/translation-files#translate-each-translation-file "Translate each translation file - Work with translation files | Angular").

4. Translate plurals and alternate expressions separately.

   1. [Translate plurals](guide/i18n/translation-files#translate-plurals "Translate plurals - Work with translation files | Angular").
   2. [Translate alternate expressions](guide/i18n/translation-files#translate-alternate-expressions "Translate alternate expressions - Work with translation files | Angular").
   3. [Translate nested expressions](guide/i18n/translation-files#translate-nested-expressions "Translate nested expressions - Work with translation files | Angular").

## [Extract the source language file](#extract-the-source-language-file)

To extract the source language file, complete the following actions.

1. Open a terminal window.
2. Change to the root directory of your project.
3. Run the following CLI command.

```
ng extract-i18n
```

The `extract-i18n` command creates a source language file named `messages.xlf` in the root directory of your project. For more information about the XML Localization Interchange File Format (XLIFF, version 1.2), see [XLIFF](https://en.wikipedia.org/wiki/XLIFF "XLIFF | Wikipedia").

Use the following [`extract-i18n`](cli/extract-i18n "ng extract-i18n | CLI | Angular") command options to change the source language file location, format, and file name.

| Command option  | Details                              |
| :-------------- | :----------------------------------- |
| `--format`      | Set the format of the output file    |
| `--out-file`    | Set the name of the output file      |
| `--output-path` | Set the path of the output directory |

### [Change the source language file location](#change-the-source-language-file-location)

To create a file in the `src/locale` directory, specify the output path as an option.

#### [`extract-i18n --output-path` example](#extract-i18n---output-path-example)

The following example specifies the output path as an option.

```
ng extract-i18n --output-path src/locale
```

### [Change the source language file format](#change-the-source-language-file-format)

The `extract-i18n` command creates files in the following translation formats.

| Translation format | Details                                                                                                                                                                                                   | File extension  |
| :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- |
| ARB                | [Application Resource Bundle](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification "ApplicationResourceBundleSpecification \| google/app-resource-bundle \| GitHub") | `.arb`          |
| JSON               | [JavaScript Object Notation](https://www.json.org "Introducing JSON \| JSON")                                                                                                                             | `.json`         |
| XLIFF 1.2          | [XML Localization Interchange File Format, version 1.2](https://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html "XLIFF Version 1.2 Specification \| Oasis Open Docs")                                   | `.xlf`          |
| XLIFF 2            | [XML Localization Interchange File Format, version 2](https://docs.oasis-open.org/xliff/xliff-core/v2.0/cos01/xliff-core-v2.0-cos01.html "XLIFF Version 2.0 \| Oasis Open Docs")                          | `.xlf`          |
| XMB                | [XML Message Bundle](https://cldr.unicode.org/development/development-process/design-proposals/xmb "XMB \| CLDR - Unicode Common Locale Data Repository \| Unicode")                                      | `.xmb` (`.xtb`) |

Specify the translation format explicitly with the `--format` command option.

**HELPFUL:** The XMB format generates `.xmb` source language files, but uses`.xtb` translation files.

#### [`extract-i18n --format` example](#extract-i18n---format-example)

The following example demonstrates several translation formats.

```
ng extract-i18n --format=xlf
ng extract-i18n --format=xlf2
ng extract-i18n --format=xmb
ng extract-i18n --format=json
ng extract-i18n --format=arb
```

### [Change the source language file name](#change-the-source-language-file-name)

To change the name of the source language file generated by the extraction tool, use the `--out-file` command option.

#### [`extract-i18n --out-file` example](#extract-i18n---out-file-example)

The following example demonstrates naming the output file.

```
ng extract-i18n --out-file source.xlf
```

## [Create a translation file for each language](#create-a-translation-file-for-each-language)

To create a translation file for a locale or language, complete the following actions.

1. [Extract the source language file](guide/i18n/translation-files#extract-the-source-language-file "Extract the source language file - Work with translation files | Angular").

2. Make a copy of the source language file to create a *translation* file for each language.

3. Rename the *translation* file to add the locale.

   messages.xlf --> messages.{locale}.xlf

4. Create a new directory at your project root named `locale`.

   src/locale

5. Move the *translation* file to the new directory.

6. Send the *translation* file to your translator.

7. Repeat the above steps for each language you want to add to your application.

### [`extract-i18n` example for French](#extract-i18n-example-for-french)

For example, to create a French translation file, complete the following actions.

1. Run the `extract-i18n` command.
2. Make a copy of the `messages.xlf` source language file.
3. Rename the copy to `messages.fr.xlf` for the French language (`fr`) translation.
4. Move the `fr` translation file to the `src/locale` directory.
5. Send the `fr` translation file to the translator.

## [Translate each translation file](#translate-each-translation-file)

Unless you are fluent in the language and have the time to edit translations, you will likely complete the following steps.

1. Send each translation file to a translator.

2. The translator uses an XLIFF file editor to complete the following actions.

   1. Create the translation.
   2. Edit the translation.

### [Translation process example for French](#translation-process-example-for-french)

To demonstrate the process, review the `messages.fr.xlf` file in the [Example Angular Internationalization application](guide/i18n/example "Example Angular Internationalization application | Angular"). The [Example Angular Internationalization application](guide/i18n/example "Example Angular Internationalization application | Angular") includes a French translation for you to edit without a special XLIFF editor or knowledge of French.

The following actions describe the translation process for French.

1. Open `messages.fr.xlf` and find the first `<trans-unit>` element. This is a *translation unit*, also known as a *text node*, that represents the translation of the `<h1>` greeting tag that was previously marked with the `i18n` attribute.

   ### src/locale/messages.fr.xlf (\<trans-unit>)

   ```
   <trans-unit id="introductionHeader" datatype="html">
     <source>Hello i18n!</source>
     <note priority="1" from="description">An introduction header for this sample</note>
     <note priority="1" from="meaning">User welcome</note>
   </trans-unit>
   ```

   The `id="introductionHeader"` is a [custom ID](guide/i18n/manage-marked-text "Manage marked text with custom IDs | Angular"), but without the `@@` prefix required in the source HTML.

2. Duplicate the `<source>... </source>` element in the text node, rename it to `target`, and then replace the content with the French text.

   ### src/locale/messages.fr.xlf (\<trans-unit>, after translation)

   ```
   <trans-unit id="introductionHeader" datatype="html">
     <source>Hello i18n!</source>
     <target>Bonjour i18n !</target>
     <note priority="1" from="description">An introduction header for this sample</note>
     <note priority="1" from="meaning">User welcome</note>
   </trans-unit>
   ```

   In a more complex translation, the information and context in the [description and meaning elements](guide/i18n/prepare#add-helpful-descriptions-and-meanings "Add helpful descriptions and meanings - Prepare component for translation | Angular") help you choose the right words for translation.

3. Translate the other text nodes. The following example displays the way to translate.

   ### src/locale/messages.fr.xlf (\<trans-unit>)

   ```
   <trans-unit id="ba0cc104d3d69bf669f97b8d96a4c5d8d9559aa3" datatype="html">
     <source>I don&apos;t output any element</source>
     <target>Je n'affiche aucun élément</target>
   </trans-unit>
   <trans-unit id="701174153757adf13e7c24a248c8a873ac9f5193" datatype="html">
     <source>Angular logo</source>
     <target>Logo d'Angular</target>
   </trans-unit>
   ```

**IMPORTANT:** Don't change the IDs for translation units. Each `id` attribute is generated by Angular and depends on the content of the component text and the assigned meaning.

If you change either the text or the meaning, then the `id` attribute changes. For more about managing text updates and IDs, see [custom IDs](guide/i18n/manage-marked-text "Manage marked text with custom IDs | Angular").

## [Translate plurals](#translate-plurals)

Add or remove plural cases as needed for each language.

**HELPFUL:** For language plural rules, see [CLDR plural rules](https://cldr.unicode.org/index/cldr-spec/plural-rules "Language Plural Rules - CLDR Charts | Unicode | GitHub").

### [`minute` `plural` example](#minute-plural-example)

To translate a `plural`, translate the ICU format match values.

* `just now`
* `one minute ago`
* `<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago`

The following example displays the way to translate.

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="5a134dee893586d02bffc9611056b9cadf9abfad" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes} }</target>
</trans-unit>
```

## [Translate alternate expressions](#translate-alternate-expressions)

Angular also extracts alternate `select` ICU expressions as separate translation units.

### [`gender` `select` example](#gender-select-example)

The following example displays a `select` ICU expression in the component template.

### app.component.html

```
<span i18n>The author is {gender, select, male {male} female {female} other {other}}</span>
```

In this example, Angular extracts the expression into two translation units. The first contains the text outside of the `select` clause, and uses a placeholder for `select` (`<x id="ICU">`):

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
  <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
  <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
</trans-unit>
```

**IMPORTANT:** When you translate the text, move the placeholder if necessary, but don't remove it. If you remove the placeholder, the ICU expression is removed from your translated application.

The following example displays the second translation unit that contains the `select` clause.

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
  <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
  <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
</trans-unit>
```

The following example displays both translation units after translation is complete.

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="f99f34ac9bd4606345071bd813858dec29f3b7d1" datatype="html">
  <source>The author is <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></source>
  <target>L'auteur est <x id="ICU" equiv-text="{gender, select, male {...} female {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="eff74b75ab7364b6fa888f1cbfae901aaaf02295" datatype="html">
  <source>{VAR_SELECT, select, male {male} female {female} other {other} }</source>
  <target>{VAR_SELECT, select, male {un homme} female {une femme} other {autre} }</target>
</trans-unit>
```

## [Translate nested expressions](#translate-nested-expressions)

Angular treats a nested expression in the same manner as an alternate expression. Angular extracts the expression into two translation units.

### [Nested `plural` example](#nested-plural-example)

The following example displays the first translation unit that contains the text outside of the nested expression.

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
  <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
  <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>
```

The following example displays the second translation unit that contains the complete nested expression.

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
</trans-unit>
```

The following example displays both translation units after translating.

### src/locale/messages.fr.xlf (\<trans-unit>)

```
<trans-unit id="972cb0cf3e442f7b1c00d7dab168ac08d6bdf20c" datatype="html">
  <source>Updated: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></source>
  <target>Mis à jour: <x id="ICU" equiv-text="{minutes, plural, =0 {...} =1 {...} other {...}}"/></target>
</trans-unit>
<trans-unit id="7151c2e67748b726f0864fc443861d45df21d706" datatype="html">
  <source>{VAR_PLURAL, plural, =0 {just now} =1 {one minute ago} other {<x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes ago by {VAR_SELECT, select, male {male} female {female} other {other} }} }</source>
  <target>{VAR_PLURAL, plural, =0 {à l'instant} =1 {il y a une minute} other {il y a <x id="INTERPOLATION" equiv-text="{{minutes}}"/> minutes par {VAR_SELECT, select, male {un homme} female {une femme} other {autre} }} }</target>
</trans-unit>
```

## [What's next](#whats-next)

----
url: https://angular.dev/api/core/Optional
----

```
@Optional();
```

## [Description](#description)

Parameter decorator to be used on constructor parameters, which marks the parameter as being an optional dependency. The DI framework provides `null` if the dependency is not found.

Can be used together with other parameter decorators that modify how dependency injection operates.

## [Usage Notes](#usage-notes)

The following code allows the possibility of a `null` result:

```
class Engine {}

        @Injectable()
        class Car {
          constructor(@Optional() public engine: Engine) {}
        }

        const injector = Injector.create({
          providers: [{provide: Car, deps: [[new Optional(), Engine]]}],
        });
        expect(injector.get(Car).engine).toBeNull();
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MarkAsTouchedOptions
----

```
    
      interface MarkAsTouchedOptions {}
    
    
```

If `true`, only marks the current field as touched. If `false` or not provided, marks the field and all its descendants as touched.

Jump to details

----
url: https://angular.dev/api/forms/signals/WithoutFieldTree
----

```
    
      type WithoutFieldTree<T> = T & {fieldTree: never}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/RouterOutlet
----

```
    
      class RouterOutlet implements OnDestroy ,OnInit ,RouterOutletContract {}
    
    
```

The name of the outlet

Emits an attached component instance when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to re-attach a previously detached subtree.

Emits a detached component instance when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to detach the subtree.

Data that will be provided to the child injector through the [`ROUTER_OUTLET_DATA`](/api/router/ROUTER_OUTLET_DATA) token.

When unset, the value of the token is `undefined` by default.

Called when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to detach the subtree

@returns`ComponentRef<any>`

Called when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs to re-attach a previously detached subtree

@paramref`ComponentRef<any>`

@paramactivatedRoute`ActivatedRoute`

@returns`void`

@returns`void`

@paramactivatedRoute`ActivatedRoute`

@paramenvironmentInjector`EnvironmentInjector`

@returns`void`

## [Description](#description)

Acts as a placeholder that Angular dynamically fills based on the current router state.

Each outlet can have a unique name, determined by the optional `name` attribute. The name cannot be set or changed dynamically. If not set, default value is "primary".

```
<router-outlet></router-outlet>
<router-outlet name='left'></router-outlet>
<router-outlet name='right'></router-outlet>
```

Named outlets can be the targets of secondary routes. The [`Route`](/api/router/Route) object for a secondary route has an `outlet` property to identify the target outlet:

`{path: <base-path>, component: <component>, outlet: <target_outlet_name>}`

Using named outlets and secondary routes, you can target multiple outlets in the same [`RouterLink`](/api/router/RouterLink) directive.

The router keeps track of separate branches in a navigation tree for each named outlet and generates a representation of that tree in the URL. The URL for a secondary route uses the following syntax to specify both the primary and secondary routes at the same time:

`http://base-path/primary-route-path(outlet-name:route-path)`

A router outlet emits an activate event when a new component is instantiated, deactivate event when a component is destroyed. An attached event emits when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs the outlet to reattach the subtree, and the detached event emits when the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs the outlet to detach the subtree.

```
<router-outlet
  (activate)='onActivate($event)'
  (deactivate)='onDeactivate($event)'
  (attach)='onAttach($event)'
  (detach)='onDetach($event)'></router-outlet>
```

## [Exported by](#exported-by)

* `RouterModule`

Jump to details

----
url: https://angular.dev/api/core/createComponent
----

```
function createComponent<C>(
  component: Type<C>,
  options: {
    environmentInjector: EnvironmentInjector;
    hostElement?: Element | undefined;
    elementInjector?: Injector | undefined;
    projectableNodes?: Node[][] | undefined;
    directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[] | undefined;
    bindings?: Binding[] | undefined;
    onError?: ((error: Error, details: ErrorDetails) => void) | undefined;
  },
): ComponentRef<C>;
```

@paramcomponent`Type<C>`

Component class reference.

@paramoptions`{ environmentInjector: EnvironmentInjector; hostElement?: Element | undefined; elementInjector?: Injector | undefined; projectableNodes?: Node[][] | undefined; directives?: (Type<unknown> | DirectiveWithBindings<unknown>)[] | undefined; bindings?: Binding[] | undefined; onError?: ((error: Error, details: ErrorDetails) => void) | undefined; }`

Set of options to use:

* `environmentInjector`: An [`EnvironmentInjector`](/api/core/EnvironmentInjector) instance to be used for the component.
* `hostElement` (optional): A DOM node that should act as a host node for the component. If not provided, Angular creates one based on the tag name used in the component selector (and falls back to using `div` if selector doesn't have tag name info).
* `elementInjector` (optional): An `ElementInjector` instance, see additional info about it [here](guide/di/hierarchical-dependency-injection#elementinjector).
* `projectableNodes` (optional): A list of DOM nodes that should be projected through [`<ng-content>`](api/core/ng-content) of the new component instance, e.g., `[[element1, element2]]`: projects `element1` and `element2` into the same `<ng-content>`. `[[element1, element2], [element3]]`: projects `element1` and `element2` into one `<ng-content>`, and `element3` into a separate `<ng-content>`.
* `directives` (optional): Directives that should be applied to the component.
* `bindings` (optional): Bindings to apply to the root component.
* `onError` (optional): A callback that will be invoked when an error occurs during the component's change detection or lifecycle hooks. Note that this will not catch errors thrown during the component's construction.

@returns`ComponentRef<C>`

ComponentRef instance that represents a given Component.

## [Usage Notes](#usage-notes)

The example below demonstrates how the [`createComponent`](/api/core/createComponent) function can be used to create an instance of a ComponentRef dynamically and attach it to an ApplicationRef, so that it gets included into change detection cycles.

Note: the example uses standalone components, but the function can also be used for non-standalone components (declared in an NgModule) as well.

```
@Component({
  template: `Hello {{ name }}!`
})
class HelloComponent {
  name = 'Angular';
}

@Component({
  template: `<div id="hello-component-host"></div>`
})
class RootComponent {}

// Bootstrap an application.
const applicationRef = await bootstrapApplication(RootComponent);

// Locate a DOM node that would be used as a host.
const hostElement = document.getElementById('hello-component-host');

// Get an `EnvironmentInjector` instance from the `ApplicationRef`.
const environmentInjector = applicationRef.injector;

// We can now create a `ComponentRef` instance.
const componentRef = createComponent(HelloComponent, {hostElement, environmentInjector});

// Last step is to register the newly created ref using the `ApplicationRef` instance
// to include the component view into change detection cycles.
applicationRef.attachView(componentRef.hostView);
componentRef.changeDetectorRef.detectChanges();
```

Jump to details

----
url: https://angular.dev/api/router/InMemoryScrollingFeature
----

```
    
      type InMemoryScrollingFeature = RouterFeature<RouterFeatureKind.InMemoryScrollingFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/JsonpClientBackend
----

```
    
      class JsonpClientBackend implements HttpBackend {}
    
    
```

@paramcallbackMap`JsonpCallbackContext`

@paramdocument`any`

@returns`JsonpClientBackend`

Processes a JSONP request and returns an event stream of the results.

@paramreq`HttpRequest<never>`

The request object.

@returns`Observable<HttpEvent<any>>`

An observable of the response events.

Jump to details

----
url: https://angular.dev/api/router/ROUTER_CONFIGURATION
----

```
    
      const ROUTER_CONFIGURATION: InjectionToken<ExtraOptions>;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/getLocaleDirection
----

Angular recommends relying on the `Intl` API for i18n. For dates and numbers, let `Intl.DateTimeFormat()` and `Intl.NumberFormat()` determine the writing direction. The `Intl` alternative [`getTextInfo`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo). has only partial support (Chromium M99 & Safari 17). 3rd party alternatives like [`rtl-detect`](https://www.npmjs.com/package/rtl-detect) can work around this issue.

## [API](#api)

```
function getLocaleDirection(locale: string): 'ltr' | 'rtl';
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. For dates and numbers, let `Intl.DateTimeFormat()` and `Intl.NumberFormat()` determine the writing direction. The `Intl` alternative [`getTextInfo`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo). has only partial support (Chromium M99 & Safari 17). 3rd party alternatives like [`rtl-detect`](https://www.npmjs.com/package/rtl-detect) can work around this issue.

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`"ltr" | "rtl"`

'rtl' or 'ltr'

Jump to details

----
url: https://angular.dev/api/core/Component
----

```
@Component({
  changeDetection?: ChangeDetectionStrategy | undefined;
  viewProviders?: Provider[] | undefined;
  templateUrl?: string | undefined;
  template?: string | undefined;
  styleUrl?: string | undefined;
  styleUrls?: string[] | undefined;
  styles?: string | string[] | undefined;
  animations?: any[] | undefined;
  encapsulation?: ViewEncapsulation | undefined;
  preserveWhitespaces?: boolean | undefined;
  standalone?: boolean | undefined;
  imports?: (readonly any[] | Type<any>)[] | undefined;
  schemas?: SchemaMetadata[] | undefined;
  override selector?: string | undefined;
  override inputs?:
    | (
        | string
        | {
            name: string;
            alias?: string | undefined;
            required?: boolean | undefined;
            transform?: ((value: any) => any) | undefined;
          }
      )[]
    | undefined;
  override outputs?: string[] | undefined;
  override providers?: Provider[] | undefined;
  override exportAs?: string | undefined;
  override queries?: { [key: string]: any } | undefined;
  override host?: { [key: string]: string } | undefined;
  override jit?: true | undefined;
  override hostDirectives?:
    | (
        | Type<unknown>
        | {
            directive: Type<unknown>;
            inputs?: string[] | undefined;
            outputs?: string[] | undefined;
          }
      )[]
    | undefined;
})
```

The change-detection strategy to use for this component.

When a component is instantiated, Angular creates a change detector, which is responsible for propagating the component's bindings. The strategy is one of:

* [`ChangeDetectionStrategy#OnPush`](/api/core/ChangeDetectionStrategy#OnPush) sets the strategy to `CheckOnce` (on demand).
* [`ChangeDetectionStrategy#Eager`](/api/core/ChangeDetectionStrategy#Eager) sets the strategy to `CheckAlways`.

**NOTE:** OnPush is enabled by default.

Defines the set of injectable objects that are visible to its view DOM children. See [example](#injecting-a-class-with-a-view-provider).

The relative path or absolute URL of a template file for an Angular component. If provided, do not supply an inline template using `template`.

An inline template for an Angular component. If provided, do not supply a template file using `templateUrl`.

One relative path or an absolute URL for file containing a CSS stylesheet to use in this component.

Relative paths or absolute URLs for files containing CSS stylesheets to use in this component.

One or more inline CSS stylesheets to use in this component.

One or more animation `trigger()` calls, containing [`state()`](api/animations/state) and `transition()` definitions. See the [Animations guide](guide/animations) and animations API documentation.

An encapsulation policy for the component's styling. Possible values:

* [`ViewEncapsulation.Emulated`](/api/core/ViewEncapsulation#Emulated): Apply modified component styles in order to emulate a native Shadow DOM CSS encapsulation behavior.
* [`ViewEncapsulation.None`](/api/core/ViewEncapsulation#None): Apply component styles globally without any sort of encapsulation.
* [`ViewEncapsulation.ShadowDom`](/api/core/ViewEncapsulation#ShadowDom): Use the browser's native Shadow DOM API to encapsulate styles.

If not supplied, the value is taken from the [`CompilerOptions`](/api/core/CompilerOptions) which defaults to [`ViewEncapsulation.Emulated`](/api/core/ViewEncapsulation#Emulated).

If the policy is [`ViewEncapsulation.Emulated`](/api/core/ViewEncapsulation#Emulated) and the component has no [`styles`](/api/core/Component#styles) nor [`styleUrls`](/api/core/Component#styleUrls), the policy is automatically switched to [`ViewEncapsulation.None`](/api/core/ViewEncapsulation#None).

True to preserve or false to remove potentially superfluous whitespace characters from the compiled template. Whitespace characters are those matching the `\s` character class in JavaScript regular expressions. Default is false, unless overridden in compiler options.

Set `standalone` to `false` if you want to import the directive into an NgModule.

The imports property specifies the standalone component's template dependencies — those directives, components, and pipes that can be used within its template. Standalone components can import other standalone components, directives, and pipes as well as existing NgModules.

This property is only available for standalone components - specifying it for components declared in an NgModule generates a compilation error.

The set of schemas that declare elements to be allowed in a standalone component. Elements and properties that are neither Angular components nor directives must be declared in a schema.

This property is only available for standalone components - specifying it for components declared in an NgModule generates a compilation error.

```
<form>
  <input type="text">
  <input type="radio">
<form>
```

Standalone directives that should be applied to the host whenever the directive is matched. By default, none of the inputs or outputs of the host directives will be available on the host, unless they are specified in the `inputs` or `outputs` properties.

You can additionally alias inputs and outputs by putting a colon and the alias after the original input or output name. For example, if a directive applied via `hostDirectives` defines an input named `menuDisabled`, you can alias this to `disabled` by adding `'menuDisabled: disabled'` as an entry to `inputs`.

## [Description](#description)

Decorator that marks a class as an Angular component and provides configuration metadata that determines how the component should be processed, instantiated, and used at runtime.

Components are the most basic UI building block of an Angular app. An Angular app contains a tree of Angular components.

Angular components are a subset of directives, always associated with a template. Unlike other directives, only one component can be instantiated for a given element in a template.

Standalone components can be directly imported in any other standalone component or NgModule. NgModule based apps on the other hand require components to belong to an NgModule in order for them to be available to another component or application. To make a component a member of an NgModule, list it in the `declarations` field of the [`NgModule`](/api/core/NgModule) metadata.

Note that, in addition to these options for configuring a directive, you can control a component's runtime behavior by implementing life-cycle hooks. For more information, see the [Lifecycle Hooks](guide/components/lifecycle) guide.

**HELPFUL:** You may not use this interface to describe a class that is a component. Decorators do not affect the typing of the decorated classes. Use `Type<unknown>` instead of `Type<Component>`.

## [Usage Notes](#usage-notes)

### [Setting component inputs](#setting-component-inputs)

The following example creates a component with two data-bound properties, specified by the `inputs` value.

```
@Component({
  selector: 'app-bank-account',
  inputs: ['bankName', 'id: account-id'],
  template: ` Bank Name: {{ bankName }} Account Id: {{ id }} `,
})
export class BankAccountComponent {
  bankName: string | null = null;
  id: string | null = null;

  // this property is not bound, and won't be automatically updated by Angular
  normalizedBankName: string | null = null;
}

@Component({
  selector: 'app-my-input',
  template: ` <app-bank-account bankName="RBC" account-id="4747" /> `,
  imports: [BankAccountComponent],
})
export class MyInputComponent {}
```

### [Setting component outputs](#setting-component-outputs)

The following example shows two output function that emit on an interval. One emits an output every second, while the other emits every five seconds.

```
@Directive({
  selector: 'app-interval-dir',
})
export class IntervalDirComponent {
  everySecond = output<string>();
  everyFiveSeconds = output<string>();

  constructor() {
    setInterval(() => this.everySecond.emit('event'), 1000);
    setInterval(() => this.everyFiveSeconds.emit('event'), 5000);
  }
}

@Component({
  selector: 'app-my-output',
  template: `
    <app-interval-dir (everySecond)="onEverySecond()" (everyFiveSeconds)="onEveryFiveSeconds()" />
  `,
  imports: [IntervalDirComponent],
})
export class MyOutputComponent {
  onEverySecond() {
    console.log('second');
  }
  onEveryFiveSeconds() {
    console.log('five seconds');
  }
}
```

### [Injecting a class with a view provider](#injecting-a-class-with-a-view-provider)

The following simple example injects a class into a component using the view provider specified in component metadata:

```
class Greeter {
   greet(name:string) {
     return 'Hello ' + name + '!';
   }
}

@Directive({
  selector: 'needs-greeter'
})
class NeedsGreeter {
  greeter:Greeter;

  constructor(greeter:Greeter) {
    this.greeter = greeter;
  }
}

@Component({
  selector: 'greet',
  viewProviders: [
    Greeter
  ],
  template: `<needs-greeter></needs-greeter>`
})
class HelloWorld {
}
```

### [Preserving whitespace](#preserving-whitespace)

Removing whitespace can greatly reduce AOT-generated code size and speed up view creation. As of Angular 6, the default for `preserveWhitespaces` is false (whitespace is removed). To change the default setting for all components in your application, set the `preserveWhitespaces` option of the AOT compiler.

By default, the AOT compiler removes whitespace characters as follows:

* Trims all whitespaces at the beginning and the end of a template.
* Removes whitespace-only text nodes. For example,

```
<button>Action 1</button>  <button>Action 2</button>
```

becomes:

```
<button>Action 1</button><button>Action 2</button>
```

* Replaces a series of whitespace characters in text nodes with a single space. For example, `<span>\n some text\n</span>` becomes `<span> some text </span>`.
* Does NOT alter text nodes inside HTML tags such as `<pre>` or `<textarea>`, where whitespace characters are significant.

Note that these transformations can influence DOM nodes layout, although impact should be minimal.

You can override the default behavior to preserve whitespace characters in certain fragments of a template. For example, you can exclude an entire DOM sub-tree by using the `ngPreserveWhitespaces` attribute:

```
<div ngPreserveWhitespaces>
    whitespaces are preserved here
    <span>    and here </span>
</div>
```

You can force a single space to be preserved in a text node by using `&ngsp;`, which is replaced with a space character by Angular's template compiler:

```
<a>Spaces</a>&ngsp;<a>between</a>&ngsp;<a>links.</a>
<!-- compiled to be equivalent to:
 <a>Spaces</a> <a>between</a> <a>links.</a>  -->
```

Note that sequences of `&ngsp;` are still collapsed to just one space character when the `preserveWhitespaces` option is set to `false`.

```
<a>before</a>&ngsp;&ngsp;&ngsp;<a>after</a>
<!-- compiled to be equivalent to:
 <a>before</a> <a>after</a> -->
```

To preserve sequences of whitespace characters, use the `ngPreserveWhitespaces` attribute.

Jump to details

----
url: https://angular.dev/api/core/afterEveryRender
----

```
function afterEveryRender<E = never, W = never, M = never>(
  spec: {
    earlyRead?: (() => E) | undefined;
    write?: ((...args: [E] extends [never] ? [] : [E]) => W) | undefined;
    mixedReadWrite?:
      | ((
          ...args: [W] extends [never] ? ([E] extends [never] ? [] : [E]) : [W]
        ) => M)
      | undefined;
    read?:
      | ((
          ...args: [M] extends [never]
            ? [W] extends [never]
              ? [E] extends [never]
                ? []
                : [E]
              : [W]
            : [M]
        ) => void)
      | undefined;
  },
  options?: AfterRenderOptions | undefined,
): AfterRenderRef;
function afterEveryRender(
  callback: VoidFunction,
  options?: AfterRenderOptions | undefined,
): AfterRenderRef;
```

Register callbacks to be invoked each time the application finishes rendering, during the specified phases. The available phases are:

* `earlyRead` Use this phase to **read** from the DOM before a subsequent `write` callback, for example to perform custom layout that the browser doesn't natively support. Prefer the `read` phase if reading can wait until after the write phase. **Never** write to the DOM in this phase.
* `write` Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.
* `mixedReadWrite` Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if it is possible to divide the work among the other phases instead.
* `read` Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.

You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite` phases when possible, to avoid performance degradation.

Note that:

* Callbacks run in the following phase order *after each render*:

Use [`afterEveryRender`](/api/core/afterEveryRender) to read or write the DOM after each render.

### [Example](#example)

```
@Component({
  selector: 'my-cmp',
  template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
  contentRef = viewChild.required<ElementRef>('content');

  constructor() {
    afterEveryRender({
      read: () => {
        console.log('content height: ' + this.contentRef().nativeElement.scrollHeight);
      }
    });
  }
}
```

Register a callback to be invoked each time the application finishes rendering, during the `mixedReadWrite` phase.

You should prefer specifying an explicit phase for the callback instead, or you risk significant performance degradation.

Note that the callback will run

* in the order it was registered
* once per render

Use [`afterEveryRender`](/api/core/afterEveryRender) to read or write the DOM after each render.

### [Example](#example)

```
@Component({
  selector: 'my-cmp',
  template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
  contentRef = viewChild.required<ElementRef>('content');

  constructor() {
    afterEveryRender({
      read: () => {
        console.log('content height: ' + this.contentRef().nativeElement.scrollHeight);
      }
    });
  }
}
```

## [Description](#description)

Register callbacks to be invoked each time the application finishes rendering, during the specified phases. The available phases are:

* `earlyRead` Use this phase to **read** from the DOM before a subsequent `write` callback, for example to perform custom layout that the browser doesn't natively support. Prefer the `read` phase if reading can wait until after the write phase. **Never** write to the DOM in this phase.
* `write` Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.
* `mixedReadWrite` Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if it is possible to divide the work among the other phases instead.
* `read` Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.

You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite` phases when possible, to avoid performance degradation.

Note that:

* Callbacks run in the following phase order *after each render*:

Use [`afterEveryRender`](/api/core/afterEveryRender) to read or write the DOM after each render.

### [Example](#example)

```
@Component({
  selector: 'my-cmp',
  template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
  contentRef = viewChild.required<ElementRef>('content');

  constructor() {
    afterEveryRender({
      read: () => {
        console.log('content height: ' + this.contentRef().nativeElement.scrollHeight);
      }
    });
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/globals/getDirectives
----

```
function getDirectives(node: Node): {}[];
```

@paramnode`Node`

DOM node for which to get the directives.

@returns`{}[]`

Array of directives associated with the node.

## [Usage Notes](#usage-notes)

Given the following DOM structure:

```
<app-root>
  <button my-button></button>
  <my-comp></my-comp>
</app-root>
```

Calling [`getDirectives`](/api/core/globals/getDirectives) on `<button>` will return an array with an instance of the `MyButton` directive that is associated with the DOM node.

Calling [`getDirectives`](/api/core/globals/getDirectives) on `<my-comp>` will return an empty array.

Jump to details

----
url: https://angular.dev/api/forms/PatternValidator
----

## [API](/api/forms/PatternValidator#api)

```
    
      class PatternValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the pattern bound to this directive.

Determines whether this validator should be active or not based on an input. Base class implementation checks whether an input is defined (if the value is different from `null` and `undefined`). Validator classes that extend this base class can override this function with the logic specific to a particular validator directive.

@paraminput`unknown`

@returns`boolean`

## [Description](/api/forms/PatternValidator#description)

A directive that adds regex pattern validation to controls marked with the `pattern` attribute. The regex must match the entire control value. The directive is provided with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) multi-provider list.

## [Exported by](/api/forms/PatternValidator#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](/api/forms/PatternValidator#usage-notes)

### [Adding a pattern validator](/api/forms/PatternValidator#adding-a-pattern-validator)

The following example shows how to add a pattern validator to an input attached to an ngModel binding.

```
<input name="firstName" ngModel pattern="[a-zA-Z ]*">
```

Jump to details

----
url: https://angular.dev/reference/migrations/route-lazy-loading
----

This schematic helps developers to convert eagerly loaded component routes to lazy loaded routes. This allows the build process to split the production bundle into smaller chunks, to avoid a big JS bundle that includes all routes, which negatively affects initial page load of an application.

Run the schematic using the following command:

```
ng generate @angular/core:route-lazy-loading
```

### [`path` config option](#path-config-option)

By default, migration will go over the entire application. If you want to apply this migration to a subset of the files, you can pass the path argument as shown below:

```
ng generate @angular/core:route-lazy-loading --path src/app/sub-component
```

The value of the path parameter is a relative path within the project.

### [How does it work?](#how-does-it-work)

The schematic will attempt to find all the places where the application routes as defined:

* [`RouterModule.forRoot`](/api/router/RouterModule#forRoot) and [`RouterModule.forChild`](/api/router/RouterModule#forChild)
* [`Router.resetConfig`](/api/router/Router#resetConfig)
* [`provideRouter`](/api/router/provideRouter)
* variables of type [`Routes`](/api/router/Routes) or `Route[]` (e.g. `const routes: Routes = [{...}]`)

The migration will check all the components in the routes, check if they are standalone and eagerly loaded, and if so, it will convert them to lazy loaded routes.

#### [Before](#before)

### app.module.ts

```
import {Home} from './home';

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'home',
        // Home is standalone and eagerly loaded
        component: Home,
      },
    ]),
  ],
})
export class AppModule {}
```

#### [After](#after)

### app.module.ts

```
@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'home',
        // ↓ Home is now lazy loaded
        loadComponent: () => import('./home').then((m) => m.Home),
      },
    ]),
  ],
})
export class AppModule {}
```

This migration will also collect information about all the components declared in NgModules and output the list of routes that use them (including corresponding location of the file). Consider making those components standalone and run this migration again. You can use an existing migration ([see](reference/migrations/standalone)) to convert those components to standalone.

----
url: https://angular.dev/api/cdk/testing/stopHandlingAutoChangeDetectionStatus
----

```
function stopHandlingAutoChangeDetectionStatus(): void;
```

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/testing/RouterTestingModule
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

Deprecation warning

Use `provideRouter` or `RouterModule`/`RouterModule.forRoot` instead. This module was previously used to provide a helpful collection of test fakes, most notably those for `Location` and `LocationStrategy`. These are generally not required anymore, as `MockPlatformLocation` is provided in [`TestBed`](/api/core/testing/TestBed) by default. However, you can use them directly with [`provideLocationMocks`](/api/common/testing/provideLocationMocks).

## [API](/api/router/testing/RouterTestingModule#api)

```
    
      class RouterTestingModule {}
    
    
```

@paramroutes`Routes`

@paramconfig`any`

@returns`ModuleWithProviders<RouterTestingModule>`

## [Description](/api/router/testing/RouterTestingModule#description)

Sets up the router to be used for testing.

The modules sets up the router to be used for testing. It provides spy implementations of `Location` and `LocationStrategy`.

## [Usage Notes](/api/router/testing/RouterTestingModule#usage-notes)

### [Example](/api/router/testing/RouterTestingModule#example)

```
beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [
      RouterModule.forRoot(
        [{path: '', component: BlankCmp}, {path: 'simple', component: SimpleCmp}]
      )
    ]
  });
});
```

Jump to details

----
url: https://angular.dev/api/upgrade/static/getAngularJSGlobal
----

```
function getAngularJSGlobal(): any;
```

@returns`any`

Jump to details

----
url: https://angular.dev/guide/components/advanced-configuration
----

## [ChangeDetectionStrategy](#changedetectionstrategy)

The [`@Component`](/api/core/Component) decorator accepts a `changeDetection` option that controls the component's **change detection mode**. There are two change detection mode options.

**[`ChangeDetectionStrategy.Eager`](/api/core/ChangeDetectionStrategy#Eager)/`Default`** is an optional mode. In this mode, Angular checks whether the component's DOM needs an update whenever any activity may have occurred application-wide. Activities that trigger this checking include user interaction, network response, timers, and more.

**[`ChangeDetectionStrategy.OnPush`](/api/core/ChangeDetectionStrategy#OnPush)** is the default strategy (since v22). This mode reduces the amount of checking Angular needs to perform. In this mode, the framework only checks if a component's DOM needs an update when:

* A component input has changes as a result of a binding in a template, or
* An event listener in this component runs
* The component is explicitly marked for check, via [`ChangeDetectorRef.markForCheck`](/api/core/ChangeDetectorRef#markForCheck) or something which wraps it, like [`AsyncPipe`](/api/common/AsyncPipe).

Additionally, when an OnPush component is checked, Angular *also* checks all of its ancestor components, traversing upwards through the application tree.

## [PreserveWhitespaces](#preservewhitespaces)

By default, Angular removes and collapses superfluous whitespace in templates, most commonly from newlines and indentation. You can change this setting by explicitly setting `preserveWhitespaces` to `true` in a component's metadata.

## [Custom element schemas](#custom-element-schemas)

By default, Angular throws an error when it encounters an unknown HTML element. You can disable this behavior for a component by including [`CUSTOM_ELEMENTS_SCHEMA`](/api/core/CUSTOM_ELEMENTS_SCHEMA) in the `schemas` property in your component metadata.

```
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';

@Component({
  ...,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: '<some-unknown-component />'
})
export class ComponentWithCustomElements { }
```

Angular also provides [`NO_ERRORS_SCHEMA`](/api/core/NO_ERRORS_SCHEMA), which allows any element and any property.

----
url: https://angular.dev/api/common/KeyValue
----

```
    
      interface KeyValue<K, V> {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/DestroyRef
----

```
    
      abstract class DestroyRef {}
    
    
```

Registers a destroy callback in a given lifecycle scope. Returns a cleanup function that can be invoked to unregister the callback.

@paramcallback`() => void`

@returns`() => void`

Usage notes

### [Example](#example)

```
const destroyRef = inject(DestroyRef);

// register a destroy callback
const unregisterFn = destroyRef.onDestroy(() => doSomethingOnDestroy());

// stop the destroy callback from executing if needed
unregisterFn();
```

Indicates whether the instance has already been destroyed.

Jump to details

----
url: https://angular.dev/guide/aria/accordion
----

An accordion organizes related content into expandable and collapsible sections, reducing page scrolling and helping users focus on relevant information. Each section has a trigger button and a content panel. Clicking a trigger toggles the visibility of its associated panel.

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="basic-accordion" [multiExpandable]="false">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  position: relative;
}

h3:focus-within::before,
h3:hover::before {
  content: '';
  position: absolute;
  height: 100%;
  width: 2px;
  background-color: var(--vivid-pink);
  top: 0;
  left: 0;
}

h3:not(:first-of-type) {
  border-block-start: 1px solid var(--senary-contrast);
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-expanded='true'] {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  position: relative;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  margin-left: 1rem;
}

.expand-icon::before,
.expand-icon::after {
  content: '';
  position: absolute;
  width: 100%;
  height: 2px;
  top: 50%;
  background-color: var(--quaternary-contrast);
  transition: .3s ease-out;
}

.expand-icon::after {
  transform: rotate(90deg);
}

.expand-icon__expanded::before {
  transform: translateY(-50%) rotate(-90deg);
  opacity: 0;
}

.expand-icon__expanded::after {
  transform: translateY(-50%) rotate(0);
  background-color: var(--electric-violet);
}
```

## [Usage](#usage)

Accordions work well for organizing content into logical groups where users typically need to view one section at a time.

**Use accordions when:**

* Displaying FAQs with multiple questions and answers
* Organizing long forms into manageable sections
* Reducing scrolling on content-heavy pages
* Progressively disclosing related information

**Avoid accordions when:**

* Building navigation menus (use the [Menu](guide/aria/menu) component instead)
* Creating tabbed interfaces (use the [Tabs](guide/aria/tabs) component instead)
* Showing a single collapsible section (use a disclosure pattern instead)
* Users need to see multiple sections simultaneously (consider a different layout)

## [Features](#features)

* **Expansion modes** - Control whether one or multiple panels can be open at the same time
* **Keyboard navigation** - Navigate between triggers using arrow keys, Home, and End
* **Lazy rendering** - Content is only created when a panel expands, improving initial load performance
* **Disabled states** - Disable the entire group or individual triggers
* **Focus management** - Control whether disabled items can receive keyboard focus
* **Programmatic control** - Expand, collapse, or toggle panels from your component code
* **RTL support** - Automatic support for right-to-left languages

## [Examples](#examples)

### [Single expansion mode](#single-expansion-mode)

Set `[multiExpandable]="false"` to allow only one panel to be open at a time. Opening a new panel automatically closes any previously open panel.

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="basic-accordion" [multiExpandable]="false">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  position: relative;
}

h3:focus-within::before,
h3:hover::before {
  content: '';
  position: absolute;
  height: 100%;
  width: 2px;
  background-color: var(--vivid-pink);
  top: 0;
  left: 0;
}

h3:not(:first-of-type) {
  border-block-start: 1px solid var(--senary-contrast);
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-expanded='true'] {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  position: relative;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  margin-left: 1rem;
}

.expand-icon::before,
.expand-icon::after {
  content: '';
  position: absolute;
  width: 100%;
  height: 2px;
  top: 50%;
  background-color: var(--quaternary-contrast);
  transition: .3s ease-out;
}

.expand-icon::after {
  transform: rotate(90deg);
}

.expand-icon__expanded::before {
  transform: translateY(-50%) rotate(-90deg);
  opacity: 0;
}

.expand-icon__expanded::after {
  transform: translateY(-50%) rotate(0);
  background-color: var(--electric-violet);
}
```

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="material-accordion" [multiExpandable]="false">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" [expanded]="true">
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  border-block-start: 1px solid var(--quinary-contrast);
  border-inline: 1px solid var(--quinary-contrast);
}

h3:focus-within,
h3:hover {
  background-color: var(--septenary-contrast);
}

h3:first-of-type {
  border-radius: 1rem 1rem 0 0;
}

h3:last-of-type {
  border-block-end: 1px solid var(--quinary-contrast);
  border-radius: 0 0 1rem 1rem;
}

h3:last-of-type:has([aria-expanded='true']) {
  border-block-end: 0;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
  border-inline: 1px solid var(--quinary-contrast);
  border-radius: 0 0 1rem 1rem;
  border-block-end: 1px solid var(--quinary-contrast);
  margin-block-end: 1rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  font-size: 1.5rem;
  color: var(--quaternary-contrast);
  transition: transform .3s ease-out;
}

.expand-icon__expanded {
  transform: rotate(180deg);
}
```

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="retro-accordion" [multiExpandable]="false">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger1.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">👻</div>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger2.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">💎💎💎</div>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger3.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">🐸 ribbit...ribbit...</div>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P';

  --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background));
  --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--retro-button-text-color);
  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

h3:focus-within,
h3:hover {
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(1px, 1px);
  outline-offset: 6px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

h3:has([aria-disabled='true']) {
  opacity: 0.5;
  cursor: default;
}

.treasure-box {
  margin: 0;
  padding: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 5rem;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionPanel] {
  padding: 1rem;
}
```

This mode works well for FAQs or situations where you want users to focus on one answer at a time.

### [Multiple expansion mode](#multiple-expansion-mode)

Set `[multiExpandable]="true"` to allow multiple panels to be open simultaneously. Users can expand as many panels as needed without closing others.

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="basic-accordion">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  position: relative;
}

h3:focus-within::before,
h3:hover::before {
  content: '';
  position: absolute;
  height: 100%;
  width: 2px;
  background-color: var(--vivid-pink);
  top: 0;
  left: 0;
}

h3:not(:first-of-type) {
  border-block-start: 1px solid var(--senary-contrast);
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-expanded='true'] {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  position: relative;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  margin-left: 1rem;
}

.expand-icon::before,
.expand-icon::after {
  content: '';
  position: absolute;
  width: 100%;
  height: 2px;
  top: 50%;
  background-color: var(--quaternary-contrast);
  transition: .3s ease-out;
}

.expand-icon::after {
  transform: rotate(90deg);
}

.expand-icon__expanded::before {
  transform: translateY(-50%) rotate(-90deg);
  opacity: 0;
}

.expand-icon__expanded::after {
  transform: translateY(-50%) rotate(0);
  background-color: var(--electric-violet);
}
```

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="material-accordion">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  border-block-start: 1px solid var(--quinary-contrast);
  border-inline: 1px solid var(--quinary-contrast);
}

h3:focus-within,
h3:hover {
  background-color: var(--septenary-contrast);
}

h3:first-of-type {
  border-radius: 1rem 1rem 0 0;
}

h3:last-of-type {
  border-block-end: 1px solid var(--quinary-contrast);
  border-radius: 0 0 1rem 1rem;
}

h3:last-of-type:has([aria-expanded='true']) {
  border-block-end: 0;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
  border-inline: 1px solid var(--quinary-contrast);
  border-radius: 0 0 1rem 1rem;
  border-block-end: 1px solid var(--quinary-contrast);
  margin-block-end: 1rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  font-size: 1.5rem;
  color: var(--quaternary-contrast);
  transition: transform .3s ease-out;
}

.expand-icon__expanded {
  transform: rotate(180deg);
}
```

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="retro-accordion">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger1.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">👻</div>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger2.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">💎💎💎</div>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger3.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">🐸 ribbit...ribbit...</div>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P';

  --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background));
  --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--retro-button-text-color);
  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

h3:focus-within,
h3:hover {
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(1px, 1px);
  outline-offset: 6px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

h3:has([aria-disabled='true']) {
  opacity: 0.5;
  cursor: default;
}

.treasure-box {
  margin: 0;
  padding: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 5rem;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionPanel] {
  padding: 1rem;
}
```

This mode is useful for form sections or when users need to compare content across multiple panels.

**NOTE:** The `multiExpandable` input defaults to `true`. Set it to `false` explicitly if you want single expansion behavior.

### [Disabled accordion items](#disabled-accordion-items)

Disable specific triggers using the `disabled` input. Control how disabled items behave during keyboard navigation using the `softDisabled` input on the accordion group.

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="basic-accordion">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled>
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
      ></span>
    </span>
  </h3>
  <div ngAccordionPanel panelId="q3" #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  position: relative;
}

h3:focus-within::before,
h3:hover::before {
  content: '';
  position: absolute;
  height: 100%;
  width: 2px;
  background-color: var(--vivid-pink);
  top: 0;
  left: 0;
}

h3:not(:first-of-type) {
  border-block-start: 1px solid var(--senary-contrast);
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-expanded='true'] {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  position: relative;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  margin-left: 1rem;
}

.expand-icon::before,
.expand-icon::after {
  content: '';
  position: absolute;
  width: 100%;
  height: 2px;
  top: 50%;
  background-color: var(--quaternary-contrast);
  transition: .3s ease-out;
}

.expand-icon::after {
  transform: rotate(90deg);
}

.expand-icon__expanded::before {
  transform: translateY(-50%) rotate(-90deg);
  opacity: 0;
}

.expand-icon__expanded::after {
  transform: translateY(-50%) rotate(0);
  background-color: var(--electric-violet);
}
```

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="material-accordion">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger" [expanded]="true">
      Which attribute tells assistive tech whether the panel is open or closed?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger1.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use <code>aria-expanded</code> on the button element. Set it to "true" when the content
        panel is visible and "false" when the content is hidden. This is a crucial state indicator
        for screen reader users.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled>
      How do you link the button to the content it controls?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger2.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        Use the <code>aria-controls</code> attribute on the button, and set its value to match the
        id of the related content panel. This establishes a programmatic relationship, allowing
        assistive technologies to jump directly to the relevant content.
      </p>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger">
      What role should the heading element containing the accordion button have?
      <span
        aria-hidden="true"
        class="material-symbols-outlined expand-icon"
        [class.expand-icon__expanded]="trigger3.expanded()"
        translate="no"
        >keyboard_arrow_up</span
      >
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <p>
        The element containing the button should typically have <code>role="heading"</code> with an
        appropriate <code>aria-level</code> to define the structure. This ensures the accordion
        section is recognized as a section header, making the page structure navigable for users.
      </p>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--secondary-contrast);
  box-sizing: border-box;
  border-block-start: 1px solid var(--quinary-contrast);
  border-inline: 1px solid var(--quinary-contrast);
}

h3:focus-within,
h3:hover {
  background-color: var(--septenary-contrast);
}

h3:first-of-type {
  border-radius: 1rem 1rem 0 0;
}

h3:last-of-type {
  border-block-end: 1px solid var(--quinary-contrast);
  border-radius: 0 0 1rem 1rem;
}

h3:last-of-type:has([aria-expanded='true']) {
  border-block-end: 0;
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

p {
  margin: 0;
  padding: 0 1.5rem 1.5rem 1.5rem;
  color: var(--tertiary-contrast);
  font-size: 0.875rem;
  border-inline: 1px solid var(--quinary-contrast);
  border-radius: 0 0 1rem 1rem;
  border-block-end: 1px solid var(--quinary-contrast);
  margin-block-end: 1rem;
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionTrigger][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

.expand-icon {
  font-size: 1.5rem;
  color: var(--quaternary-contrast);
  transition: transform .3s ease-out;
}

.expand-icon__expanded {
  transform: rotate(180deg);
}
```

### TS

```
import {Component} from '@angular/core';
import {
  AccordionGroup,
  AccordionTrigger,
  AccordionPanel,
  AccordionContent,
} from '@angular/aria/accordion';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],
})
export class App {}
```

### HTML

```
<div ngAccordionGroup class="retro-accordion">
  <h3>
    <span ngAccordionTrigger [panel]="panel1" #trigger1="ngAccordionTrigger">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger1.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel1="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">👻</div>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel2" #trigger2="ngAccordionTrigger" disabled>
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger2.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel2="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">💎💎💎</div>
    </ng-template>
  </div>

  <h3>
    <span ngAccordionTrigger [panel]="panel3" #trigger3="ngAccordionTrigger" [expanded]="true">
      Unlock Treasure Box
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        trigger3.expanded() ? 'lock_open_right' : 'lock'
      }}</span>
    </span>
  </h3>
  <div ngAccordionPanel #panel3="ngAccordionPanel">
    <ng-template ngAccordionContent>
      <div class="treasure-box">🐸 ribbit...ribbit...</div>
    </ng-template>
  </div>
</div>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-family: 'Press Start 2P';

  --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background));
  --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white);
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast),
    0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
    0px -4px 0px 0px var(--quaternary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--quaternary-contrast), 0px 4px 0px 0px var(--quaternary-contrast),
    -4px 0px 0px 0px var(--quaternary-contrast), 0px -4px 0px 0px var(--quaternary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast),
    0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
    0px -4px 0px 0px var(--quaternary-contrast), 8px 8px 0px 0px var(--quaternary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--quaternary-contrast),
    0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
    0px -4px 0px 0px var(--quaternary-contrast), 0px 0px 0px 0px var(--quaternary-contrast);
}

[ngAccordionGroup] {
  width: 500px;
}

h3 {
  font-size: 1rem;
  margin: 0;
  color: var(--retro-button-text-color);
  background-color: var(--retro-button-color);
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

h3:focus-within,
h3:hover {
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(1px, 1px);
  outline-offset: 6px;
  outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

h3:has([aria-disabled='true']) {
  opacity: 0.5;
  cursor: default;
}

.treasure-box {
  margin: 0;
  padding: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 5rem;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

[ngAccordionTrigger] {
  display: flex;
  align-items: center;
  justify-content: space-between;
  outline: none;
  cursor: pointer;
  padding: 1.5rem;
}

[ngAccordionPanel] {
  padding: 1rem;
}
```

When `[softDisabled]="true"` (the default), disabled items can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled items are skipped entirely during keyboard navigation.

### [Lazy content rendering](#lazy-content-rendering)

Use the `ngAccordionContent` directive on an [`ng-template`](/api/core/ng-template) to defer rendering content until the panel expands. This improves performance for accordions with heavy content like images, charts, or complex components.

```
<div ngAccordionGroup>
  <div>
    <button ngAccordionTrigger [panel]="panel1">Trigger Text</button>
    <div ngAccordionPanel #panel1="ngAccordionPanel">
      <ng-template ngAccordionContent>
        <!-- This content only renders when the panel opens -->
        <img src="large-image.jpg" alt="Description" />
        <app-expensive-component />
      </ng-template>
    </div>
  </div>
</div>
```

By default, content is removed from the DOM when the panel collapses. Set `[preserveContent]="true"` to keep it, for example when the panel contains a video that shouldn't reset on collapse.

## [Testing](#testing)

Angular Aria provides component harnesses for testing accordion components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {AccordionGroupHarness} from '@angular/aria/accordion/testing';
import {MyAccordionComponent} from './my-accordion'; // Your component

describe('MyAccordionComponent', () => {
  let fixture: ComponentFixture<MyAccordionComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyAccordionComponent],
    });

    fixture = TestBed.createComponent(MyAccordionComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should allow expanding panels', async () => {
    // Load the accordion group harness
    const group = await loader.getHarness(AccordionGroupHarness);

    // Get all individual accordions (items) in the group
    const accordions = await group.getAccordions();
    expect(accordions.length).toBe(3);

    // Verify initial state (first expanded, others collapsed)
    expect(await accordions[0].isExpanded()).toBe(true);
    expect(await accordions[1].isExpanded()).toBe(false);

    // Expand the second panel
    await accordions[1].expand();

    // Verify updated state
    expect(await accordions[1].isExpanded()).toBe(true);
    // If multiExpandable is false, the first one should now be collapsed
    expect(await accordions[0].isExpanded()).toBe(false);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`AccordionGroup`](/api/aria/accordion/AccordionGroup)
* [`AccordionTrigger`](/api/aria/accordion/AccordionTrigger)
* [`AccordionPanel`](/api/aria/accordion/AccordionPanel)
* [`AccordionContent`](/api/aria/accordion/AccordionContent)

----
url: https://angular.dev/guide/legacy-animations/complex-sequences
----

**IMPORTANT:** The `@angular/animations` package is now deprecated. The Angular team recommends using native CSS with `animate.enter` and `animate.leave` for animations for all new code. Learn more at the new enter and leave [animation guide](/guide/animations). Also see [Migrating away from Angular's Animations package](guide/animations/migration) to learn how you can start migrating to pure CSS animations in your apps.

So far, we've learned simple animations of single HTML elements. Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page. You can choose to run multiple animations in parallel, or run discrete animations sequentially, one following another.

The functions that control complex animation sequences are:

| Functions                                | Details                                                        |
| :--------------------------------------- | :------------------------------------------------------------- |
| [`query()`](/api/animations/query)       | Finds one or more inner HTML elements.                         |
| [`stagger()`](/api/animations/stagger)   | Applies a cascading delay to animations for multiple elements. |
| [`group()`](api/animations/group)        | Runs multiple animation steps in parallel.                     |
| [`sequence()`](/api/animations/sequence) | Runs animation steps one after another.                        |

## [The query() function](#the-query-function)

Most complex animations rely on the [`query()`](/api/animations/query) function to find child elements and apply animations to them. Basic examples include:

| Examples                                                                                        | Details                                                                                                                                                                                          |
| :---------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`query()`](/api/animations/query) followed by [`animate()`](/api/animations/animate)           | Used to query simple HTML elements and directly apply animations to them.                                                                                                                        |
| [`query()`](/api/animations/query) followed by [`animateChild()`](/api/animations/animateChild) | Used to query child elements, which themselves have animation metadata applied to them and trigger such animations (which would otherwise be blocked by the current/parent element's animation). |

The first argument of [`query()`](/api/animations/query) is a [css selector](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) string which can also contain the following Angular-specific tokens:

| Tokens              | Details                                      |
| :------------------ | :------------------------------------------- |
| `:enter` `:leave`   | For entering/leaving elements.               |
| `:animating`        | For elements currently animating.            |
| `@*` `@triggerName` | For elements with any—or a specific—trigger. |
| `:self`             | The animating element itself.                |

### Entering and Leaving Elements

Not all child elements are actually considered as entering/leaving; this can, at times, be counterintuitive and confusing. Please see the [query api docs](api/animations/query#entering-and-leaving-elements) for more information.

You can also see an illustration of this in the animations example (introduced in the animations [introduction section](guide/legacy-animations#about-this-guide)) under the Querying tab.

## [Animate multiple elements using query() and stagger() functions](#animate-multiple-elements-using-query-and-stagger-functions)

After querying child elements via [`query()`](/api/animations/query), the [`stagger()`](/api/animations/stagger) function lets you define a timing gap between each item, animating elements with a delay between them.

The following example demonstrates how to use the [`query()`](/api/animations/query) and [`stagger()`](/api/animations/stagger) functions to animate a list (of heroes) adding each in sequence, with a slight delay, from top to bottom.

* Use [`query()`](/api/animations/query) to look for an element entering the page that meets certain criteria

* For each of these elements, use [`style()`](/api/animations/style) to set the same initial style for the element. Make it transparent and use `transform` to move it out of position so that it can slide into place.

* Use [`stagger()`](/api/animations/stagger) to delay each animation by 30 milliseconds

* Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it

### hero-list-page.ts

```
animations: [
  trigger('pageAnimations', [
    transition(':enter', [
      query('.hero', [
        style({opacity: 0, transform: 'translateY(-100px)'}),
        stagger(30, [
          animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({opacity: 1, transform: 'none'})),
        ]),
      ]),
    ]),
  ]),
```

## [Parallel animation using group() function](#parallel-animation-using-group-function)

You've seen how to add a delay between each successive animation. But you might also want to configure animations that happen in parallel. For example, you might want to animate two CSS properties of the same element but use a different `easing` function for each one. For this, you can use the animation [`group()`](api/animations/group) function.

**HELPFUL:** The [`group()`](api/animations/group) function is used to group animation *steps*, rather than animated elements.

The following example uses [`group()`](api/animations/group)s on both `:enter` and `:leave` for two different timing configurations, thus applying two independent animations to the same element in parallel.

### hero-list-groups.ts (excerpt)

```
animations: [
  trigger('flyInOut', [
    state(
      'in',
      style({
        width: '*',
        transform: 'translateX(0)',
        opacity: 1,
      }),
    ),
    transition(':enter', [
      style({width: 10, transform: 'translateX(50px)', opacity: 0}),
      group([
        animate(
          '0.3s 0.1s ease',
          style({
            transform: 'translateX(0)',
            width: '*',
          }),
        ),
        animate(
          '0.3s ease',
          style({
            opacity: 1,
          }),
        ),
      ]),
    ]),
    transition(':leave', [
      group([
        animate(
          '0.3s ease',
          style({
            transform: 'translateX(50px)',
            width: 10,
          }),
        ),
        animate(
          '0.3s 0.2s ease',
          style({
            opacity: 0,
          }),
        ),
      ]),
    ]),
  ]),
],
```

## [Sequential vs. parallel animations](#sequential-vs-parallel-animations)

Complex animations can have many things happening at once. But what if you want to create an animation involving several animations happening one after the other? Earlier you used [`group()`](api/animations/group) to run multiple animations all at the same time, in parallel.

A second function called [`sequence()`](/api/animations/sequence) lets you run those same animations one after the other. Within [`sequence()`](/api/animations/sequence), the animation steps consist of either [`style()`](/api/animations/style) or [`animate()`](/api/animations/animate) function calls.

* Use [`style()`](/api/animations/style) to apply the provided styling data immediately.
* Use [`animate()`](/api/animations/animate) to apply styling data over a given time interval.

## [Filter animation example](#filter-animation-example)

Take a look at another animation on the example page. Under the Filter/Stagger tab, enter some text into the **Search Heroes** text box, such as `Magnet` or `tornado`.

The filter works in real time as you type. Elements leave the page as you type each new letter and the filter gets progressively stricter. The heroes list gradually re-enters the page as you delete each letter in the filter box.

The HTML template contains a trigger called `filterAnimation`.

### hero-list-page.html

```
<label for="search">Search heroes: </label>
<input
  type="text"
  id="search"
  #criteria
  (input)="updateCriteria(criteria.value)"
  placeholder="Search heroes"
/>

<ul class="heroes" [@filterAnimation]="heroesTotal">
  @for (hero of heroes; track hero) {
    <li class="hero">
      <div class="inner">
        <span class="badge">{{ hero.id }}</span>
        <span class="name">{{ hero.name }}</span>
      </div>
    </li>
  }
</ul>
```

The `filterAnimation` in the component's decorator contains three transitions.

### hero-list-page.ts

```
@Component({
  animations: [
    trigger('filterAnimation', [
      transition(':enter, * => 0, * => -1', []),
      transition(':increment', [
        query(
          ':enter',
          [
            style({opacity: 0, width: 0}),
            stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]),
          ],
          {optional: true},
        ),
      ]),
      transition(':decrement', [
        query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]),
      ]),
    ]),
  ],
})
export class HeroListPage implements OnInit {
  heroesTotal = -1;

  get heroes() {
    return this._heroes;
  }
  private _heroes: Hero[] = [];

  ngOnInit() {
    this._heroes = HEROES;
  }

  updateCriteria(criteria: string) {
    criteria = criteria ? criteria.trim() : '';

    this._heroes = HEROES.filter((hero) =>
      hero.name.toLowerCase().includes(criteria.toLowerCase()),
    );
    const newTotal = this.heroes.length;

    if (this.heroesTotal !== newTotal) {
      this.heroesTotal = newTotal;
    } else if (!criteria) {
      this.heroesTotal = -1;
    }
  }
}
```

The code in this example performs the following tasks:

* Skips animations when the user first opens or navigates to this page (the filter animation narrows what is already there, so it only works on elements that already exist in the DOM)
* Filters heroes based on the search input's value

For each change:

* Hides an element leaving the DOM by setting its opacity and width to 0

* Animates an element entering the DOM over 300 milliseconds. During the animation, the element assumes its default width and opacity.

* If there are multiple elements entering or leaving the DOM, staggers each animation starting at the top of the page, with a 50-millisecond delay between each element

## [Animating the items of a reordering list](#animating-the-items-of-a-reordering-list)

Although Angular animates correctly `*ngFor` list items out of the box, it will not be able to do so if their ordering changes. This is because it will lose track of which element is which, resulting in broken animations. The only way to help Angular keep track of such elements is by assigning a [`TrackByFunction`](/api/core/TrackByFunction) to the [`NgForOf`](/api/common/NgForOf) directive. This makes sure that Angular always knows which element is which, thus allowing it to apply the correct animations to the correct elements all the time.

**IMPORTANT:** If you need to animate the items of an `*ngFor` list and there is a possibility that the order of such items will change during runtime, always use a [`TrackByFunction`](/api/core/TrackByFunction).

## [Animations and Component View Encapsulation](#animations-and-component-view-encapsulation)

Angular animations are based on the components DOM structure and do not directly take [View Encapsulation](guide/components/styling#style-scoping) into account, this means that components using [`ViewEncapsulation.Emulated`](/api/core/ViewEncapsulation#Emulated) behave exactly as if they were using [`ViewEncapsulation.None`](/api/core/ViewEncapsulation#None) ([`ViewEncapsulation.ShadowDom`](/api/core/ViewEncapsulation#ShadowDom) and [`ViewEncapsulation.ExperimentalIsolatedShadowDom`](/api/core/ViewEncapsulation#ExperimentalIsolatedShadowDom) behave differently as we'll discuss shortly).

For example if the [`query()`](/api/animations/query) function (which you'll see more of in the rest of the Animations guide) were to be applied at the top of a tree of components using the emulated view encapsulation, such query would be able to identify (and thus animate) DOM elements on any depth of the tree.

On the other hand the [`ViewEncapsulation.ShadowDom`](/api/core/ViewEncapsulation#ShadowDom) and [`ViewEncapsulation.ExperimentalIsolatedShadowDom`](/api/core/ViewEncapsulation#ExperimentalIsolatedShadowDom) changes the component's DOM structure by "hiding" DOM elements inside [`ShadowRoot`](https://developer.mozilla.org/docs/Web/API/ShadowRoot) elements. Such DOM manipulations do prevent some of the animations implementation to work properly since it relies on simple DOM structures and doesn't take `ShadowRoot` elements into account. Therefore it is advised to avoid applying animations to views incorporating components using the ShadowDom view encapsulation.

## [Animation sequence summary](#animation-sequence-summary)

Angular functions for animating multiple elements start with [`query()`](/api/animations/query) to find inner elements; for example, gathering all images within a `<div>`. The remaining functions, [`stagger()`](/api/animations/stagger), [`group()`](api/animations/group), and [`sequence()`](/api/animations/sequence), apply cascades or let you control how multiple animation steps are applied.

## [More on Angular animations](#more-on-angular-animations)

You might also be interested in the following:

----
url: https://angular.dev/api/core/Resource
----

```
    
      interface Resource<T> {
}
    
    
```

The current value of the [`Resource`](/api/core/Resource), or throws an error if the resource is in an error state.

The current status of the [`Resource`](/api/core/Resource), which describes what the resource is currently doing and what can be expected of its `value`.

When in the `error` state, this returns the last known error from the [`Resource`](/api/core/Resource).

Whether this resource is loading a new value (or reloading the existing one).

The current state of this resource, represented as a [`ResourceSnapshot`](/api/core/ResourceSnapshot).

Whether this resource has a valid current value.

This function is reactive.

@paramthis`T extends undefined ? this : never`

@returns`this is Resource<Exclude<T, undefined>>`

@returns`boolean`

## [Description](#description)

A Resource is an asynchronous dependency (for example, the results of an API call) that is managed and delivered through signals.

The usual way of creating a [`Resource`](/api/core/Resource) is through the [`resource`](/api/core/resource) function, but various other APIs may present [`Resource`](/api/core/Resource) instances to describe their own concepts.

Jump to details

----
url: https://angular.dev/api/common/upgrade/$locationShimProvider
----

```
    
      class $locationShimProvider {}
    
    
```

@paramngUpgrade`UpgradeModule`

@paramlocation`Location`

@paramplatformLocation`PlatformLocation`

@paramurlCodec`UrlCodec`

@paramlocationStrategy`LocationStrategy`

@returns`$locationShimProvider`

Factory method that returns an instance of the $locationShim

@returns`$locationShim`

Stub method used to keep API compatible with AngularJS. This setting is configured through the LocationUpgradeModule's `config` method in your Angular app.

@paramprefix`string | undefined`

@returns`void`

Stub method used to keep API compatible with AngularJS. This setting is configured through the LocationUpgradeModule's `config` method in your Angular app.

@parammode`any`

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/DeprecatedResolve
----

```
    
      type DeprecatedResolve = DeprecatedGuard | any
    
    
```

Jump to details

----
url: https://angular.dev/api/aria/accordion/AccordionTrigger
----

```
    
      class AccordionTrigger implements OnInit ,OnDestroy {}
    
    
```

A reference to the trigger element.

The associated AccordionPanel.

The unique identifier for the trigger.

The unique identifier for the corresponding trigger panel.

Whether the trigger is disabled.

Whether the corresponding panel is expanded.

Whether the trigger is active.

@returns`void`

@returns`void`

The trigger that toggles the visibility of its associated `ngAccordionPanel`.

This directive requires the `panel` input be set to the template reference of the `ngAccordionPanel` it controls. When clicked, it will expand or collapse the panel. It also handles keyboard interactions for navigation within the `ngAccordionGroup`. It applies `role="button"` and manages `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility. The `disabled` input can be used to disable the trigger.

```
<button ngAccordionTrigger [panel]="panel">
  Accordion Trigger Text
</button>
```

Jump to details

----
url: https://angular.dev/api/core/globals/getDirectiveMetadata
----

```
function getDirectiveMetadata(
  directiveOrComponentInstance: any,
): AngularComponentDebugMetadata | AngularDirectiveDebugMetadata | null;
```

@paramdirectiveOrComponentInstance`any`

Instance of a directive or component

@returns`AngularComponentDebugMetadata | AngularDirectiveDebugMetadata | null`

metadata of the passed directive or component

Jump to details

----
url: https://angular.dev/api/platform-browser/REMOVE_STYLES_ON_COMPONENT_DESTROY
----

```
    
      const REMOVE_STYLES_ON_COMPONENT_DESTROY: InjectionToken<boolean>;
    
    
```

## [Description](#description)

A DI token that indicates whether styles of destroyed components should be removed from DOM.

By default, the value is set to `true`.

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/14-routerLink
----

[](/tutorials/learn-angular/13-define-a-route)[](/tutorials/learn-angular/15-forms)

[](/tutorials/learn-angular/13-define-a-route)[](/tutorials/learn-angular/15-forms)

In the app's current state, the entire page refreshes when we click on an internal link that exists within the app. While this may not seem significant with a small app, this can have performance implications for larger pages with more content where users have to redownload assets and run calculations again.

**NOTE:** Learn more about [adding routes to your application in the in-depth guide](/guide/routing/define-routes#adding-the-router-to-your-application).

In this activity, you'll learn how to leverage the [`RouterLink`](/api/router/RouterLink) directive to make the most use of Angular Router.

***

1. ### [Import `RouterLink` directive](#import-routerlink-directive)

   In `app.ts` add the [`RouterLink`](/api/router/RouterLink) directive import to the existing import statement from `@angular/router` and add it to the `imports` array of your component decorator.

   ```
   ...
   import { RouterLink, RouterOutlet } from '@angular/router';

   @Component({
     imports: [RouterLink, RouterOutlet],
     ...
   })
   ```

2. ### [Add a `routerLink` to template](#add-a-routerlink-to-template)

   To use the [`RouterLink`](/api/router/RouterLink) directive, replace the `href` attributes with [`routerLink`](/api/router/RouterLinkWithHref). Update the template with this change.

   ```
   import { RouterLink, RouterOutlet } from '@angular/router';

   @Component({
     ...
     template: `
       ...
       <a routerLink="/">Home</a>
       <a routerLink="/user">User</a>
       ...
     `,
     imports: [RouterLink, RouterOutlet],
   })
   ```

When you click on the links in the navigation now, you should not see any blinking and only the content of the page itself (i.e., `router-outlet`) being changed 🎉

Great job learning about routing with Angular. This is just the surface of the [`Router`](/api/router/Router) API, to learn more check out the [Angular Router Documentation](guide/routing).

----
url: https://angular.dev/api/common/ImageLoader
----

```
    
      type ImageLoader = (config: ImageLoaderConfig) => string
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG0403
----

This error means that an NgModule that was used for bootstrapping an application is missing key information for Angular to proceed with the bootstrap process.

The error happens when the NgModule `bootstrap` property is missing (or is an empty array) in the [`@NgModule`](/api/core/NgModule) annotation and there is no `ngDoBootstrap` lifecycle hook defined on that NgModule class.

More information about the bootstrapping process can be found in [this guide](guide/ngmodules/overview).

The following examples will trigger the error.

```
@NgModule({
  declarations: [App],
  imports: [BrowserModule, AppRoutingModule],
  providers: [],
})
export class AppModule {}

// The `AppModule` is used for bootstrapping, but the `@NgModule.bootstrap` field is missing.
platformBrowser().bootstrapModule(AppModule);
```

```
@NgModule({
  declarations: [App],
  imports: [BrowserModule, AppRoutingModule],
  providers: [],
  bootstrap: [],
})
export class AppModule {}

// The `AppModule` is used for bootstrapping, but the `@NgModule.bootstrap` field contains an empty array.
platformBrowser().bootstrapModule(AppModule);
```

## [Debugging the error](#debugging-the-error)

Please make sure that the NgModule that is used for bootstrapping is set up correctly:

* either the `bootstrap` property exists (and contains a non-empty array) in the [`@NgModule`](/api/core/NgModule) annotation
* or the `ngDoBootstrap` method exists on the NgModule class

----
url: https://angular.dev/guide/templates
----

In-depth Guides

Templates

# Template syntax

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/templates/overview.md "Edit this page")

In Angular, a template is a chunk of HTML. Use special syntax within a template to leverage many of Angular's features.

**TIP:** Check out Angular's [Essentials](essentials/templates) before diving into this comprehensive guide.

Every Angular component has a **template** that defines the [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) that the component renders onto the page. By using templates, Angular is able to automatically keep your page up-to-date as data changes.

Templates are usually found within either the `template` property of a `*.ts` file or the `*.html` file. To learn more, check out the [in-depth components guide](/guide/components).

## [How do templates work?](#how-do-templates-work)

Templates are based on [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) syntax, with additional features such as built-in template functions, data binding, event listening, variables, and more.

Angular compiles templates into JavaScript in order to build up an internal understanding of your application. One of the benefits of this are built-in rendering optimizations that Angular applies to your application automatically.

### [Differences from standard HTML](#differences-from-standard-html)

Some differences between templates and standard HTML syntax include:

* Comments in the template source code are not included in the rendered output
* Component and directive elements can be self-closed (e.g., `<UserProfile />`)
* Attributes with certain characters (i.e., `[]`, `()`, etc.) have special meaning to Angular. See [binding docs](guide/templates/binding) and [adding event listeners docs](guide/templates/event-listeners) for more information.
* The `@` character has a special meaning to Angular for adding dynamic behavior, such as [control flow](guide/templates/control-flow), to templates. You can include a literal `@` character by escaping it as an HTML entity code (`&commat;` or `&#64;`).
* Angular ignores and collapses unnecessary whitespace characters. See [whitespace in templates](guide/templates/whitespace) for more details.
* Angular may add comment nodes to a page as placeholders for dynamic content, but developers can ignore these.

In addition, while most HTML syntax is valid template syntax, Angular does not support `<script>` element in templates. For more information, see the [Security](best-practices/security) page.

## [What's next?](#whats-next)

You might also be interested in the following:

| Topics                                                                      | Details                                                                                 |
| :-------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| [Binding dynamic text, properties, and attributes](guide/templates/binding) | Bind dynamic data to text, properties and attributes.                                   |
| [Adding event listeners](guide/templates/event-listeners)                   | Respond to events in your templates.                                                    |
| [Two-way binding](guide/templates/two-way-binding)                          | Simultaneously binds a value and propagate changes.                                     |
| [Control flow](guide/templates/control-flow)                                | Conditionally show, hide and repeat elements.                                           |
| [Pipes](guide/templates/pipes)                                              | Transform data declaratively.                                                           |
| [Slotting child content with ng-content](guide/templates/ng-content)        | Control how components render content.                                                  |
| [Create template fragments with ng-template](guide/templates/ng-template)   | Declare a template fragment.                                                            |
| [Grouping elements with ng-container](guide/templates/ng-container)         | Group multiple elements together or mark a location for rendering.                      |
| [Variables in templates](guide/templates/variables)                         | Learn about variable declarations.                                                      |
| [Deferred loading with @defer](guide/templates/defer)                       | Create deferrable views with `@defer`.                                                  |
| [Expression syntax](guide/templates/expression-syntax)                      | Learn similarities and differences between Angular expressions and standard JavaScript. |
| [Whitespace in templates](guide/templates/whitespace)                       | Learn how Angular handles whitespace.                                                   |

----
url: https://angular.dev/api/elements/NgElementStrategy
----

```
    
      interface NgElementStrategy {}
    
    
```

@paramelement`HTMLElement`

@returns`void`

@returns`void`

@parampropName`string`

@returns`any`

@parampropName`string`

@paramvalue`string`

@paramtransform`((value: any) => any) | undefined`

@returns`void`

Jump to details

----
url: https://angular.dev/api/forms/signals/requiredError
----

```
function requiredError(
  options: WithFieldTree<ValidationErrorOptions>,
): RequiredValidationError;
function requiredError(
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<RequiredValidationError>;
```

Create a required error associated with the target field

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`RequiredValidationError`

Create a required error

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<RequiredValidationError>`

Jump to details

----
url: https://angular.dev/api/router/withEnabledBlockingInitialNavigation
----

```
function withEnabledBlockingInitialNavigation(): EnabledBlockingInitialNavigationFeature;
```

@returns`EnabledBlockingInitialNavigationFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Description](#description)

Configures initial navigation to start before the root component is created.

The bootstrap is blocked until the initial navigation is complete. This should be set in case you use [server-side rendering](guide/ssr), but do not enable [hydration](guide/hydration) for your application.

## [Usage Notes](#usage-notes)

Basic example of how you can enable this navigation behavior:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withEnabledBlockingInitialNavigation())
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/api/router/InMemoryScrollingOptions
----

```
    
      interface InMemoryScrollingOptions {}
    
    
```

```
class App {
  movieData = signal<MovieData | null>(null);
  private router = inject(Router);
  private viewportScroller = inject(ViewportScroller);
  private changeDetectorRef = inject(ChangeDetectorRef);

  constructor() {
   this.router.events.pipe(filter((event: Event): event is Scroll => event instanceof Scroll)
    ).subscribe(e => {
      fetch('http://example.com/movies.json').then(response => {
        this.movieData.set(response.json());

        if (e.position) {
          this.viewportScroller.scrollToPosition(e.position);
        }
      });
    });
  }
}
```

Jump to details

----
url: https://angular.dev/api/common/UpperCasePipe
----

```
{{ value_expression | uppercase }}
```

## [API](#api)

```
    
      class UpperCasePipe implements PipeTransform {
}
    
    
```

@paramvalue`string`

The string to transform to upper case.

@returns`string`

@paramvalue`null | undefined`

@returns`null`

@paramvalue`string | null | undefined`

@returns`string | null`

## [Description](#description)

Transforms text to all upper case.

## [Exported by](#exported-by)

* `CommonModule`

Jump to details

----
url: https://angular.dev/api/common/HashLocationStrategy
----

```
    
      class HashLocationStrategy extends LocationStrategy implements OnDestroy {}
    
    
```

@param\_platformLocation`PlatformLocation`

@param\_baseHref`string | undefined`

@returns`HashLocationStrategy`

@paramfn`LocationChangeListener`

@returns`void`

@returns`string`

@paramincludeHash`boolean`

@returns`string`

@paraminternal`string`

@returns`string`

@paramstate`any`

@paramtitle`string`

@parampath`string`

@paramqueryParams`string`

@returns`void`

@paramstate`any`

@paramtitle`string`

@parampath`string`

@paramqueryParams`string`

@returns`void`

@returns`void`

@returns`void`

@returns`unknown`

@paramrelativePosition`number`

@returns`void`

## [Description](#description)

A [`LocationStrategy`](/api/common/LocationStrategy) used to configure the [`Location`](/api/common/Location) service to represent its state in the [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the browser's URL.

For instance, if you call `location.go('/foo')`, the browser's URL will become `example.com#/foo`.

## [Usage Notes](#usage-notes)

### [Example](#example)

```
import {HashLocationStrategy, Location, LocationStrategy} from '@angular/common';
import {Component} from '@angular/core';

@Component({
  selector: 'hash-location',
  providers: [Location, {provide: LocationStrategy, useClass: HashLocationStrategy}],
  template: `
    <h1>HashLocationStrategy</h1>
    Current URL is: <code>{{ location.path() }}</code
    ><br />
    Normalize: <code>/foo/bar/</code> is: <code>{{ location.normalize('foo/bar') }}</code
    ><br />
  `,
  standalone: false,
})
export class HashLocationComponent {
  location: Location;
  constructor(location: Location) {
    this.location = location;
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/EnvironmentInjector
----

```
    
      abstract class EnvironmentInjector implements Injector {
}
    
    
```

Runs the given function in the context of this [`EnvironmentInjector`](/api/core/EnvironmentInjector).

Within the function's stack frame, [`inject`](api/core/inject) can be used to inject dependencies from this injector. Note that [`inject`](/api/core/inject) is only usable synchronously, and cannot be used in any asynchronous callbacks or after any `await` points.

@deprecated

use the standalone function [`runInInjectionContext`](/api/core/runInInjectionContext) instead

@paramfn`() => ReturnT`

the closure to be run in the context of this injector

@returns`ReturnT`

the return value of the function, if any

@returns`void`

Indicates whether the instance has already been destroyed.

Jump to details

----
url: https://angular.dev/cli/generate/ai-config
----

```
ng generate ai-config [options]
```

Generates AI configuration files for Angular projects. This schematic creates instruction files (e.g. AGENTS.md, CLAUDE.md) and Angular MCP server configuration, improving the quality of AI-generated code and suggestions.

## [Options](#options)

`tool`

Specifies which AI tools to generate configuration files (AGENTS.md, CLAUDE.md, MCP server config) for.

Value Type`array`Allowed Values`claude-code`, `cursor`, `gemini-cli`, `none`, `open-ai-codex`, `vscode`Default`none`

Jump to details

----
url: https://angular.dev/api/core/output
----

```
    
      
    
    
```

@paramopts`OutputOptions | undefined`

@returns`OutputEmitterRef<T>`

Usage notes

To use [`output()`](/api/core/output), import the function from `@angular/core`.

```
import {output} from '@angular/core';
```

Inside your component, introduce a new class member and initialize it with a call to [`output`](/api/core/output).

```
@Directive({
  ...
})
export class MyDir {
  nameChange = output<string>();    // OutputEmitterRef<string>
  onClick    = output();            // OutputEmitterRef<void>
}
```

You can emit values to consumers of your directive, by using the `emit` method from [`OutputEmitterRef`](/api/core/OutputEmitterRef).

```
updateName(newName: string): void {
  this.nameChange.emit(newName);
}
```

## [Usage Notes](#usage-notes)

To use [`output()`](/api/core/output), import the function from `@angular/core`.

```
import {output} from '@angular/core';
```

Inside your component, introduce a new class member and initialize it with a call to [`output`](/api/core/output).

```
@Directive({
  ...
})
export class MyDir {
  nameChange = output<string>();    // OutputEmitterRef<string>
  onClick    = output();            // OutputEmitterRef<void>
}
```

You can emit values to consumers of your directive, by using the `emit` method from [`OutputEmitterRef`](/api/core/OutputEmitterRef).

```
updateName(newName: string): void {
  this.nameChange.emit(newName);
}
```

Jump to details

----
url: https://angular.dev/cli/analytics/disable
----

```
ng analytics disable [options]
```

```
ng analytics off [options]
```

Disables analytics gathering and reporting for the user.

## [Options](#options)

`global`

Alias`g`

Configure analytics gathering and reporting globally in the caller's home directory.

Value Type`boolean`Default`false`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/4-control-flow-if
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/3-composing-components)[](/tutorials/learn-angular/5-control-flow-for)

[](/tutorials/learn-angular/3-composing-components)[](/tutorials/learn-angular/5-control-flow-for)

Deciding what to display on the screen for a user is a common task in application development. Many times, the decision is made programmatically using conditions.

To express conditional displays in templates, Angular uses the `@if` template syntax.

**NOTE:** Learn more about [control flow in the essentials guide](/essentials/templates#control-flow-with-if-and-for).

In this activity, you'll learn how to use conditionals in templates.

***

The syntax that enables the conditional display of elements in a template is `@if`.

Here's an example of how to use the `@if` syntax in a component:

```
@Component({
  ...
  template: `
    @if (isLoggedIn) {
      <p>Welcome back, Friend!</p>
    }
  `,
})
export class App {
  isLoggedIn = true;
}
```

Two things to take note of:

* There is an `@` prefix for the `if` because it is a special type of syntax called [Angular template syntax](guide/templates)
* For applications that still use `*ngIf`, please refer to the [Angular documentation for NgIf](guide/directives/structural-directives) for more information.

1. ### [Create a property called `isServerRunning`](#create-a-property-called-isserverrunning)

   In the `App` class, add a `boolean` property called `isServerRunning`, set the initial value to `true`.

2. ### [Use `@if` in the template](#use-if-in-the-template)

   Update the template to display the message `Yes, the server is running` if the value of `isServerRunning` is `true`.

3. ### [Use `@else` in the template](#use-else-in-the-template)

   Now Angular supports native template syntax for defining the else case with the `@else` syntax. Update the template to display the message `No, the server is not running` as the else case.

   Here's an example:

   ```
   template: `
   @if (isServerRunning) {
     ...
   } @else {
     ...
   }
   `;
   ```

   Add your code to fill in the missing markup.

This type of functionality is called conditional control flow. Next you'll learn how to repeat items in a template.

----
url: https://angular.dev/api/cdk/testing/LocatorFnResult
----

```
    
      type LocatorFnResult<T extends (HarnessQuery<any> | string)[]> = {  [I in keyof T]: T[I] extends new (...args: any[]) => infer C // Map `ComponentHarnessConstructor<C>` to `C`.    ? C    : // Map `HarnessPredicate<C>` to `C`.      T[I] extends {harnessType: new (...args: any[]) => infer C}      ? C      : // Map `string` to `TestElement`.        T[I] extends string        ? TestElement        : // Map everything else to `never` (should not happen due to the type constraint on `T`).          never;}[number]
    
    
```

## [Description](#description)

The result type obtained when searching using a particular list of queries. This type depends on the particular items being queried.

* If one of the queries is for a `ComponentHarnessConstructor<C1>`, it means that the result might be a harness of type `C1`
* If one of the queries is for a `HarnessPredicate<C2>`, it means that the result might be a harness of type `C2`
* If one of the queries is for a `string`, it means that the result might be a [`TestElement`](/api/cdk/testing/TestElement).

Since we don't know for sure which query will match, the result type if the union of the types for all possible results.

## [Usage Notes](#usage-notes)

### [Example](#example)

The type:

```
LocatorFnResult<[
  ComponentHarnessConstructor<MyHarness>,
  HarnessPredicate<MyOtherHarness>,
  string
]>
```

is equivalent to:

```
MyHarness | MyOtherHarness | TestElement
```

Jump to details

----
url: https://angular.dev/api/core/InjectionToken
----

```
    
      class InjectionToken<T> {
}
    
    
```

@deprecated

The `providedIn: NgModule` or `providedIn:'any'` options are deprecated. Please use the other signature.

@param\_desc`string`

@paramoptions`{ providedIn: Type<any> | "any"; factory: () => T; }`

@returns`InjectionToken`

@param\_desc`string`

Description for the token, used only for debugging purposes, it should but does not need to be unique

@paramoptions`{ providedIn?: Type<any> | "any" | "root" | "platform" | null | undefined; factory: () => T; } | undefined`

Options for the token's usage, as described above

@returns`InjectionToken`

@returns`string`

## [Description](#description)

Creates a token that can be used in a DI Provider.

Use an [`InjectionToken`](/api/core/InjectionToken) whenever the type you are injecting is not reified (does not have a runtime representation) such as when injecting an interface, callable type, array or parameterized type.

[`InjectionToken`](/api/core/InjectionToken) is parameterized on `T` which is the type of object which will be returned by the [`Injector`](/api/core/Injector). This provides an additional level of type safety.

**Important Note**: Ensure that you use the same instance of the [`InjectionToken`](/api/core/InjectionToken) in both the provider and the injection call. Creating a new instance of [`InjectionToken`](/api/core/InjectionToken) in different places, even with the same description, will be treated as different tokens by Angular's DI system, leading to a `NullInjectorError`.

```
const TOKEN = new InjectionToken<MyInterface>('SomeToken');

// Setting up the provider using the same token instance
const providers = [
  {provide: TOKEN, useValue: {someProperty: 'exampleValue'}}, // Mock value for MyInterface
];

// Creating the injector with the provider
const injector = Injector.create({providers});

// Retrieving the value using the same token instance
const myInterface = injector.get(TOKEN);
// myInterface is inferred to be MyInterface.
```

When creating an [`InjectionToken`](/api/core/InjectionToken), you can optionally specify a factory function which returns (possibly by creating) a default value of the parameterized type `T`. This sets up the [`InjectionToken`](/api/core/InjectionToken) using this factory as a provider as if it was defined explicitly in the application's root injector. If the factory function, which takes zero arguments, needs to inject dependencies, it can do so using the [`inject`](api/core/inject) function. As you can see in the Tree-shakable InjectionToken example below.

Additionally, if a `factory` is specified you can also specify the `providedIn` option, which overrides the above behavior and marks the token as belonging to a particular [`@NgModule`](/api/core/NgModule) (note: this option is now deprecated). As mentioned above, `'root'` is the default value for `providedIn`.

The `providedIn: NgModule` and `providedIn: 'any'` options are deprecated.

## [Usage Notes](#usage-notes)

### [Basic Examples](#basic-examples)

### [Plain InjectionToken](#plain-injectiontoken)

```
const BASE_URL = new InjectionToken<string>('BaseUrl');
      const injector = Injector.create({
        providers: [{provide: BASE_URL, useValue: 'http://localhost'}],
      });
      const url = injector.get(BASE_URL);
      // Note: since `BASE_URL` is `InjectionToken<string>`
      // `url` is correctly inferred to be `string`
      expect(url).toBe('http://localhost');
```

### [Tree-shakable InjectionToken](#tree-shakable-injectiontoken)

```
class MyService {
        constructor(readonly myDep: MyDep) {}
      }

      const MY_SERVICE_TOKEN = new InjectionToken<MyService>('Manually constructed MyService', {
        providedIn: 'root',
        factory: () => new MyService(inject(MyDep)),
      });

      const instance = injector.get(MY_SERVICE_TOKEN);
      expect(instance instanceof MyService).toBeTruthy();
      expect(instance.myDep instanceof MyDep).toBeTruthy();
```

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/toSignal
----

```
function toSignal<T>(source: any): Signal<T | undefined>;
function toSignal<T>(
  source: any,
  options: NoInfer<ToSignalOptions<T | undefined>> & {
    initialValue?: undefined;
    requireSync?: false | undefined;
  },
): Signal<T | undefined>;
function toSignal<T>(
  source: any,
  options: NoInfer<ToSignalOptions<T | null>> & {
    initialValue?: null | undefined;
    requireSync?: false | undefined;
  },
): Signal<T | null>;
function toSignal<T>(
  source: any,
  options: NoInfer<ToSignalOptions<T>> & {
    initialValue?: undefined;
    requireSync: true;
  },
): Signal<T>;
function toSignal<T, U extends T>(
  source: any,
  options: NoInfer<ToSignalOptions<T | U>> & {
    initialValue: U;
    requireSync?: false | undefined;
  },
): Signal<T | U>;
```

@paramsource`any`

@returns`Signal<T | undefined>`

@paramsource`any`

@paramoptions`NoInfer<ToSignalOptions<T | undefined>> & { initialValue?: undefined; requireSync?: false | undefined; }`

@returns`Signal<T | undefined>`

@paramsource`any`

@paramoptions`NoInfer<ToSignalOptions<T | null>> & { initialValue?: null | undefined; requireSync?: false | undefined; }`

@returns`Signal<T | null>`

@paramsource`any`

@paramoptions`NoInfer<ToSignalOptions<T>> & { initialValue?: undefined; requireSync: true; }`

@returns`Signal<T>`

@paramsource`any`

@paramoptions`NoInfer<ToSignalOptions<T | U>> & { initialValue: U; requireSync?: false | undefined; }`

@returns`Signal<T | U>`

## [Description](#description)

Get the current value of an `Observable` as a reactive `Signal`.

[`toSignal`](/api/core/rxjs-interop/toSignal) returns a `Signal` which provides synchronous reactive access to values produced by the given `Observable`, by subscribing to that `Observable`. The returned `Signal` will always have the most recent value emitted by the subscription, and will throw an error if the `Observable` errors.

With `requireSync` set to `true`, [`toSignal`](/api/core/rxjs-interop/toSignal) will assert that the `Observable` produces a value immediately upon subscription. No `initialValue` is needed in this case, and the returned signal does not include an `undefined` type.

By default, the subscription will be automatically cleaned up when the current [injection context](guide/di/dependency-injection-context) is destroyed. For example, when [`toSignal`](/api/core/rxjs-interop/toSignal) is called during the construction of a component, the subscription will be cleaned up when the component is destroyed. If an injection context is not available, an explicit `Injector` can be passed instead.

If the subscription should persist until the `Observable` itself completes, the `manualCleanup` option can be specified instead, which disables the automatic subscription teardown. No injection context is needed in this configuration as well.

Jump to details

----
url: https://angular.dev/api/upgrade/static/downgradeComponent
----

```
function downgradeComponent(info: {
  component: Type<any>;
  downgradedModule?: string | undefined;
  propagateDigest?: boolean | undefined;
  inputs?: string[] | undefined;
  outputs?: string[] | undefined;
  selectors?: string[] | undefined;
}): any;
```

@paraminfo`{ component: Type<any>; downgradedModule?: string | undefined; propagateDigest?: boolean | undefined; inputs?: string[] | undefined; outputs?: string[] | undefined; selectors?: string[] | undefined; }`

contains information about the Component that is being downgraded:

* `component: Type<any>`: The type of the Component that will be downgraded
* `downgradedModule?: string`: The name of the downgraded module (if any) that the component "belongs to", as returned by a call to [`downgradeModule()`](/api/upgrade/static/downgradeModule). It is the module, whose corresponding Angular module will be bootstrapped, when the component needs to be instantiated.

(This option is only necessary when using \`downgradeModule()\` to downgrade more than one Angular module.) - \`propagateDigest?: boolean\`: Whether to perform [`detectChanges`](/api/core/ChangeDetectorRef#detectChanges) on the component on every [`$digest`](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest). If set to \`false\`, change detection will still be performed when any of the component's inputs changes. (Default: true)

@returns`any`

a factory function that can be used to register the component in an AngularJS module.

## [Description](#description)

A helper function that allows an Angular component to be used from AngularJS.

*Part of the [upgrade/static](api?query=upgrade%2Fstatic) library for hybrid upgrade apps that support AOT compilation*

This helper function returns a factory function to be used for registering an AngularJS wrapper directive for "downgrading" an Angular component.

## [Usage Notes](#usage-notes)

### [Examples](#examples)

Let's assume that you have an Angular component called `ng2Heroes` that needs to be made available in AngularJS templates.

```
// This Angular component will be "downgraded" to be used in AngularJS
@Component({
  selector: 'ng2-heroes',
  // This template uses the upgraded `ng1-hero` component
  // Note that because its element is compiled by Angular we must use camelCased attribute names
  template: `<header><ng-content selector="h1"></ng-content></header>
    <ng-content selector=".extra"></ng-content>
    <div *ngFor="let hero of heroes()">
      <ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"
        ><strong>Super Hero</strong></ng1-hero
      >
    </div>
    <button (click)="addHero.emit()">Add Hero</button>`,
  standalone: false,
})
export class Ng2HeroesComponent {
  heroes = input<Hero[]>([]);
  addHero = output<void>();
  removeHero = output<Hero>();
}
```

We must create an AngularJS [directive](https://docs.angularjs.org/guide/directive) that will make this Angular component available inside AngularJS templates. The [`downgradeComponent()`](/api/upgrade/static/downgradeComponent) function returns a factory function that we can use to define the AngularJS directive that wraps the "downgraded" component.

```
// This directive will act as the interface to the "downgraded" Angular component
ng1AppModule.directive('ng2Heroes', downgradeComponent({component: Ng2HeroesComponent}));
```

For more details and examples on downgrading Angular components to AngularJS components please visit the [Upgrade guide](https://angular.io/guide/upgrade#using-angular-components-from-angularjs-code).

Jump to details

----
url: https://angular.dev/api/animations/AUTO_STYLE
----

```
    
      const AUTO_STYLE: string;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/testing/provideLocationMocks
----

```
function provideLocationMocks(): Provider[];
```

@returns`Provider[]`

Jump to details

----
url: https://angular.dev/api/core/Testability
----

```
    
      class Testability implements PublicTestability {}
    
    
```

@param\_ngZone`NgZone`

@paramregistry`TestabilityRegistry`

@paramtestabilityGetter`GetTestability`

@returns`Testability`

Whether an associated application is stable

@returns`boolean`

Wait for the application to be stable with a timeout. If the timeout is reached before that happens, the callback receives a list of the macro tasks that were pending, otherwise null.

@paramdoneCb`Function`

The callback to invoke when Angular is stable or the timeout expires whichever comes first.

@paramtimeout`number | undefined`

Optional. The maximum time to wait for Angular to become stable. If not specified, whenStable() will wait forever.

@paramupdateCb`Function | undefined`

Optional. If specified, this callback will be invoked whenever the set of pending macrotasks changes. If this callback returns true doneCb will not be invoked and no further updates will be issued.

@returns`void`

Find providers by name

@paramusing`any`

The root element to search from

@paramprovider`string`

The name of binding variable

@paramexactMatch`boolean`

Whether using exactMatch

@returns`any[]`

## [Description](#description)

The Testability service provides testing hooks that can be accessed from the browser.

Angular applications bootstrapped using an NgModule (via [`@NgModule.bootstrap`](/api/core/NgModule#bootstrap) field) will also instantiate Testability by default (in both development and production modes).

For applications bootstrapped using the `bootstrapApplication` function, Testability is not included by default. You can include it into your applications by getting the list of necessary providers using the `provideProtractorTestingSupport()` function and adding them into the `options.providers` array. Example:

```
import {provideProtractorTestingSupport} from '@angular/platform-browser';

await bootstrapApplication(RootComponent, providers: [provideProtractorTestingSupport()]);
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MapToErrorsFn
----

```
    
      type MapToErrorsFn<TValue, TResult, TPathKind extends PathKind = PathKind.Root> = (  result: TResult,  ctx: FieldContext<TValue, TPathKind>,) => TreeValidationResult
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MinLengthValidationError
----

```
    
      class MinLengthValidationError extends BaseNgValidationError {}
    
    
```

@paramminLength`number`

@paramoptions`ValidationErrorOptions | undefined`

@returns`MinLengthValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/core/EventEmitter
----

```
    
      interface EventEmitter<T> extends Subject<T>, OutputRef<T> {
}
    
    
```

Emits an event containing a given value.

@paramvalue`T | undefined`

The value to emit.

@returns`void`

Registers handlers for events emitted by this instance.

@paramnext`((value: T) => void) | undefined`

When supplied, a custom handler for emitted events.

@paramerror`((error: any) => void) | undefined`

When supplied, a custom handler for an error notification from this emitter.

@paramcomplete`(() => void) | undefined`

When supplied, a custom handler for a completion notification from this emitter.

@returns`Subscription`

Registers handlers for events emitted by this instance.

@paramobserverOrNext`any`

When supplied, a custom handler for emitted events, or an observer object.

@paramerror`any`

When supplied, a custom handler for an error notification from this emitter.

@paramcomplete`any`

When supplied, a custom handler for a completion notification from this emitter.

@returns`Subscription`

## [Usage Notes](#usage-notes)

Extends [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject) for Angular by adding the `emit()` method.

In the following example, a component defines two output properties that create event emitters. When the title is clicked, the emitter emits an open or close event to toggle the current visibility state.

```
@Component({
  selector: 'zippy',
  template: `
  <div class="zippy">
    <div (click)="toggle()">Toggle</div>
    <div [hidden]="!visible">
      <ng-content></ng-content>
    </div>
 </div>`})
export class Zippy {
  visible: boolean = true;
  @Output() open: EventEmitter<any> = new EventEmitter();
  @Output() close: EventEmitter<any> = new EventEmitter();

  toggle() {
    this.visible = !this.visible;
    if (this.visible) {
      this.open.emit(null);
    } else {
      this.close.emit(null);
    }
  }
}
```

Access the event object with the `$event` argument passed to the output event handler:

```
<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
```

Jump to details

----
url: https://angular.dev/api/animations/group
----

```
function group(
  steps: AnimationMetadata[],
  options?: AnimationOptions | null,
): AnimationGroupMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramsteps`AnimationMetadata[]`

An array of animation step objects.

* When steps are defined by [`style()`](/api/animations/style) or [`animate()`](/api/animations/animate) function calls, each call within the group is executed instantly.
* To specify offset styles to be applied at a later time, define steps with [`keyframes()`](/api/animations/keyframes), or use [`animate()`](/api/animations/animate) calls with a delay value. For example:

```
group([
animate("1s", style({ background: "black" })),
animate("2s", style({ color: "white" }))
])
```

@paramoptions`AnimationOptions | null`

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation.

@returns`AnimationGroupMetadata`

## [Usage Notes](#usage-notes)

Grouped animations are useful when a series of styles must be animated at different starting times and closed off at different ending times.

When called within a [`sequence()`](/api/animations/sequence) or a [`transition()`](/api/animations/transition) call, does not continue to the next instruction until all of the inner animation steps have completed.

Jump to details

----
url: https://angular.dev/api/common/registerLocaleData
----

```
function registerLocaleData(data: any, localeId?: any, extraData?: any): void;
```

@paramdata`any`

@paramlocaleId`any`

@paramextraData`any`

@returns`void`

## [Description](#description)

Register global data to be used internally by Angular. See the ["I18n guide"](guide/i18n/format-data-locale) to know how to import additional locale data.

The signature registerLocaleData(data: any, extraData?: any) is deprecated since v5.1

Jump to details

----
url: https://angular.dev/api/common/FormStyle
----

locale data getters are deprecated

## [API](#api)

```
    
      enum FormStyle {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/CreateEffectOptions
----

```
    
      interface CreateEffectOptions {}
    
    
```

The [`Injector`](/api/core/Injector) in which to create the effect.

If this is not provided, the current [injection context](guide/di/dependency-injection-context) will be used instead (via [`inject`](/api/core/inject)).

Whether the [`effect`](/api/core/effect) should require manual cleanup.

If this is `false` (the default) the effect will automatically register itself to be cleaned up with the current [`DestroyRef`](/api/core/DestroyRef).

If this is `true` and you want to use the effect outside an injection context, you still need to provide an [`Injector`](/api/core/Injector) to the effect.

@deprecated

no longer required, signal writes are allowed by default.

A debug name for the effect. Used in Angular DevTools to identify the effect.

Jump to details

----
url: https://angular.dev/api/core/Attribute
----

```
@Attribute(name: string);
```

## [Usage Notes](#usage-notes)

Suppose we have an `<input>` element and want to know its `type`.

```
<input type="text">
```

The following example uses the decorator to inject the string literal `text` in a directive.

```
@Directive({
  selector: 'input',
})
class InputAttrDirective {
  constructor(@Attribute('type') type: string) {
    // type would be 'text' in this example
  }
}
```

The following example uses the decorator in a component constructor.

```
@Component({
  selector: 'page',
  template: 'Title: {{title}}',
})
class Page {
  title: string;
  constructor(@Attribute('title') title: string) {
    this.title = title;
  }
}
```

Jump to details

----
url: https://angular.dev/guide/di/dependency-injection-context
----

The dependency injection (DI) system relies on a runtime context where the current injector is available.

This means that injectors only work when you execute code within this context.

You have an injection context available in the following situations:

* During construction (via the `constructor`) of a class instantiated by the DI system, such as an [`@Injectable`](/api/core/Injectable) or [`@Component`](/api/core/Component).
* In field initializers of such classes.
* In the factory function specified for `useFactory` of a [`Provider`](/api/core/Provider) or an [`@Injectable`](/api/core/Injectable).
* In the `factory` function specified for an [`InjectionToken`](/api/core/InjectionToken).
* Within a stack frame that runs in an injection context.

Knowing when you are in an injection context allows you to use the [`inject`](api/core/inject) function to retrieve dependencies.

**NOTE:** For basic examples of using `inject()` in class constructors and field initializers, see the [overview guide](/guide/di#where-can-inject-be-used).

## [Stack frame in context](#stack-frame-in-context)

Some APIs are designed to run within an injection context. This is the case, for example, with router guards. This allows you to use [`inject`](api/core/inject) within the guard function to access services.

Here is an example for [`CanActivateFn`](/api/router/CanActivateFn)

```
const canActivateTeam: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  return inject(PermissionsService).canActivate(inject(UserToken), route.params.id);
};
```

## [Run within an injection context](#run-within-an-injection-context)

If you need to run a function within an injection context without already being in one, you can use [`runInInjectionContext`](/api/core/runInInjectionContext). This requires access to an injector, such as the [`EnvironmentInjector`](/api/core/EnvironmentInjector):

### hero.service.ts

```
@Service()
export class HeroService {
  private environmentInjector = inject(EnvironmentInjector);

  someMethod() {
    runInInjectionContext(this.environmentInjector, () => {
      inject(SomeService); // Do what you need with the injected service
    });
  }
}
```

Note that [`inject`](/api/core/inject) returns an instance only if the injector can resolve the requested token.

## [Asserts the context](#asserts-the-context)

Angular provides the [`assertInInjectionContext`](/api/core/assertInInjectionContext) helper function to verify that the current context is an injection context and throw a clear error if it is not. Pass a reference to the calling function so the error message points to the correct API entry point. This produces a clearer, more actionable message than the default generic injection error.

```
import {ElementRef, assertInInjectionContext, inject} from '@angular/core';

export function injectNativeElement<T extends Element>(): T {
  assertInInjectionContext(injectNativeElement);
  return inject(ElementRef).nativeElement;
}
```

You can then call this helper **from an injection context** (constructor, field initializer, provider factory, or code executed via [`runInInjectionContext`](/api/core/runInInjectionContext)):

```
import {Component, inject} from '@angular/core';
import {injectNativeElement} from './dom-helpers';

@Component({/* … */})
export class PreviewCard {
  readonly hostEl = injectNativeElement<HTMLElement>(); // Field initializer runs in an injection context.

  onAction() {
    const anotherRef = injectNativeElement<HTMLElement>(); // Fails: runs outside an injection context.
  }
}
```

## [Using DI outside of a context](#using-di-outside-of-a-context)

If you call [`inject`](api/core/inject) or [`assertInInjectionContext`](/api/core/assertInInjectionContext) outside of an injection context, Angular throws [error NG0203](/errors/NG0203).

----
url: https://angular.dev/api/common/ImageConfig
----

```
    
      type ImageConfig = {  breakpoints?: number[];  placeholderResolution?: number;  disableImageSizeWarning?: boolean;  disableImageLazyLoadWarning?: boolean;}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/testing/RequestMatch
----

```
    
      interface RequestMatch {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/StaticClassSansProvider
----

```
    
      interface StaticClassSansProvider {}
    
    
```

An optional class to instantiate for the `token`. By default, the `provide` class is instantiated.

A list of `token`s to be resolved by the injector. The list of values is then used as arguments to the `useClass` constructor.

Jump to details

----
url: https://angular.dev/api/core/PLATFORM_INITIALIZER
----

from v19.0.0, use providePlatformInitializer instead

## [API](#api)

```
    
      const PLATFORM_INITIALIZER: InjectionToken<readonly (() => void)[]>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/HttpValidatorOptions
----

```
    
      interface HttpValidatorOptions<TValue, TResult, TPathKind extends PathKind = PathKind.Root> {}
    
    
```

A function that receives the field context and returns the url or request for the httpResource. If given a URL, the underlying httpResource will perform an HTTP GET on it.

A function that takes the httpResource result, and the current field context and maps it to a list of validation errors.

A function to handle errors thrown by httpResource (HTTP errors, network errors, etc.). Receives the error and the field context, returns a list of validation errors.

The options to use when creating the httpResource.

Duration in milliseconds to wait before triggering the async operation, or a function that returns a promise that resolves when the update should proceed.

A function that receives the field context and returns true if the async validation should be run.

Jump to details

----
url: https://angular.dev/api/common/http/HttpStatusCode
----

```
    
      enum HttpStatusCode {}
    
    
```

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/devops
----

This page is a reference for deploying and supporting production applications that use the Angular service worker. It explains how the Angular service worker fits into the larger production environment, the service worker's behavior under various conditions, and available resources and fail-safes.

## [Service worker and caching of application resources](/ecosystem/service-workers/devops#service-worker-and-caching-of-application-resources)

Imagine the Angular service worker as a forward cache or a Content Delivery Network (CDN) edge that is installed in the end user's web browser. The service worker responds to requests made by the Angular application for resources or data from a local cache, without needing to wait for the network. Like any cache, it has rules for how content is expired and updated.

### [Application versions](/ecosystem/service-workers/devops#application-versions)

In the context of an Angular service worker, a "version" is a collection of resources that represent a specific build of the Angular application. Whenever a new build of the application is deployed, the service worker treats that build as a new version of the application. This is true even if only a single file is updated. At any given time, the service worker might have multiple versions of the application in its cache and it might be serving them simultaneously. For more information, see the [Application tabs](/ecosystem/service-workers/devops#application-tabs) section.

To preserve application integrity, the Angular service worker groups all files into a version together. The files grouped into a version usually include HTML, JS, and CSS files. Grouping of these files is essential for integrity because HTML, JS, and CSS files frequently refer to each other and depend on specific content. For example, an `index.html` file might have a `<script>` tag that references `bundle.js` and it might attempt to call a function `startApp()` from within that script. Any time this version of `index.html` is served, the corresponding `bundle.js` must be served with it. For example, assume that the `startApp()` function is renamed to `runApp()` in both files. In this scenario, it is not valid to serve the old `index.html`, which calls `startApp()`, along with the new bundle, which defines `runApp()`.

This file integrity is especially important when lazy loading. A JS bundle might reference many lazy chunks, and the filenames of the lazy chunks are unique to the particular build of the application. If a running application at version `X` attempts to load a lazy chunk, but the server has already updated to version `X + 1`, the lazy loading operation fails.

The version identifier of the application is determined by the contents of all resources, and it changes if any of them change. In practice, the version is determined by the contents of the `ngsw.json` file, which includes hashes for all known content. If any of the cached files change, the file's hash changes in `ngsw.json`. This change causes the Angular service worker to treat the active set of files as a new version.

**HELPFUL:** The build process creates the manifest file, `ngsw.json`, using information from `ngsw-config.json`.

With the versioning behavior of the Angular service worker, an application server can ensure that the Angular application always has a consistent set of files.

#### [Update checks](/ecosystem/service-workers/devops#update-checks)

Every time the user opens or refreshes the application, the Angular service worker checks for updates to the application by looking for updates to the `ngsw.json` manifest. If an update is found, it is downloaded and cached automatically, and is served the next time the application is loaded.

### [Resource integrity](/ecosystem/service-workers/devops#resource-integrity)

One of the potential side effects of long caching is inadvertently caching a resource that's not valid. In a normal HTTP cache, a hard refresh or the cache expiring limits the negative effects of caching a file that's not valid. A service worker ignores such constraints and effectively long-caches the entire application. It's important that the service worker gets the correct content, so it keeps hashes of the resources to maintain their integrity.

#### [Hashed content](/ecosystem/service-workers/devops#hashed-content)

To ensure resource integrity, the Angular service worker validates the hashes of all resources for which it has a hash. For an application created with the [Angular CLI](tools/cli), this is everything in the `dist` directory covered by the user's `src/ngsw-config.json` configuration.

If a particular file fails validation, the Angular service worker attempts to re-fetch the content using a "cache-busting" URL parameter to prevent browser or intermediate caching. If that content also fails validation, the service worker considers the entire version of the application to not be valid and stops serving the application. If necessary, the service worker enters a safe mode where requests fall back on the network. The service worker doesn't use its cache if there's a high risk of serving content that is broken, outdated, or not valid.

Hash mismatches can occur for a variety of reasons:

* Caching layers between the origin server and the end user could serve stale content
* A non-atomic deployment could result in the Angular service worker having visibility of partially updated content
* Errors during the build process could result in updated resources without `ngsw.json` being updated. The reverse could also happen resulting in an updated `ngsw.json` without updated resources.

#### [Unhashed content](/ecosystem/service-workers/devops#unhashed-content)

The only resources that have hashes in the `ngsw.json` manifest are resources that were present in the `dist` directory at the time the manifest was built. Other resources, especially those loaded from CDNs, have content that is unknown at build time or are updated more frequently than the application is deployed.

If the Angular service worker does not have a hash to verify a resource is valid, it still caches its contents. At the same time, it honors the HTTP caching headers by using a policy of *stale while revalidate*. The Angular service worker continues to serve a resource even after its HTTP caching headers indicate that it is no longer valid. At the same time, it attempts to refresh the expired resource in the background. This way, broken unhashed resources do not remain in the cache beyond their configured lifetimes.

### [Application tabs](/ecosystem/service-workers/devops#application-tabs)

It can be problematic for an application if the version of resources it's receiving changes suddenly or without warning. See the [Application versions](/ecosystem/service-workers/devops#application-versions) section for a description of such issues.

The Angular service worker provides a guarantee: a running application continues to run the same version of the application. If another instance of the application is opened in a new web browser tab, then the most current version of the application is served. As a result, that new tab can be running a different version of the application than the original tab.

**IMPORTANT:** This guarantee is **stronger** than that provided by the normal web deployment model. Without a service worker, there is no guarantee that lazily loaded code is from the same version as the application's initial code.

The Angular service worker might change the version of a running application under error conditions such as:

* The current version becomes non-valid due to a failed hash.
* An unrelated error causes the service worker to enter safe mode and deactivates it temporarily.

The Angular service worker cleans up application versions when no tab is using them.

Other reasons the Angular service worker might change the version of a running application are normal events:

* The page is reloaded/refreshed.
* The page requests an update be immediately activated using the [`SwUpdate`](/api/service-worker/SwUpdate) service.

### [Service worker updates](/ecosystem/service-workers/devops#service-worker-updates)

The Angular service worker is a small script that runs in web browsers. From time to time, the service worker is updated with bug fixes and feature improvements.

The Angular service worker is downloaded when the application is first opened and when the application is accessed after a period of inactivity. If the service worker changes, it's updated in the background.

Most updates to the Angular service worker are transparent to the application. The old caches are still valid and content is still served normally. Occasionally, a bug fix or feature in the Angular service worker might require the invalidation of old caches. In this case, the service worker transparently refreshes the application from the network.

#### [Updating the service worker when only its response headers change](/ecosystem/service-workers/devops#updating-the-service-worker-when-only-its-response-headers-change)

Browsers only install a new service worker when the service worker script is byte-different from the installed one. Because `ngsw-worker.js` is usually identical across builds, changing only the response headers that the server sends with it, such as a `Content-Security-Policy`, does not update the installed service worker. The service worker keeps running with the headers it was installed with.

To make browsers install the service worker again, register it with a versioned script URL, and change the version whenever those headers change:

```
provideServiceWorker('ngsw-worker.js?v=2', {
  enabled: !isDevMode(),
  registrationStrategy: 'registerWhenStable:30000',
});
```

Registering a different script URL makes the browser fetch and install the service worker again, even if its content is unchanged. The Angular service worker resolves its caches and `ngsw.json` relative to its registration scope, not its script URL, so the query parameter doesn't affect cached content.

### [Bypassing the service worker](/ecosystem/service-workers/devops#bypassing-the-service-worker)

In some cases, you might want to bypass the service worker entirely and let the browser handle the request. An example is when you rely on a feature that is currently not supported in service workers, such as [reporting progress on uploaded files](https://github.com/w3c/ServiceWorker/issues/1141).

To bypass the service worker, set `ngsw-bypass` as a request header, or as a query parameter. The value of the header or query parameter is ignored and can be empty or omitted.

### [Service worker requests when the server can't be reached](/ecosystem/service-workers/devops#service-worker-requests-when-the-server-cant-be-reached)

The service worker processes all requests unless the [service worker is explicitly bypassed](/ecosystem/service-workers/devops#bypassing-the-service-worker). The service worker either returns a cached response or sends the request to the server, depending on the state and configuration of the cache. The service worker only caches responses to non-mutating requests, such as `GET` and `HEAD`.

If the service worker receives an error from the server or it doesn't receive a response, it returns an error status that indicates the result of the call. For example, if the service worker doesn't receive a response, it creates a [504 Gateway Timeout](https://developer.mozilla.org/docs/Web/HTTP/Status/504) status to return. The `504` status in this example could be returned because the server is offline or the client is disconnected.

## [Debugging the Angular service worker](/ecosystem/service-workers/devops#debugging-the-angular-service-worker)

Occasionally, it might be necessary to examine the Angular service worker in a running state to investigate issues or whether it's operating as designed. Browsers provide built-in tools for debugging service workers and the Angular service worker itself includes useful debugging features.

### [Locating and analyzing debugging information](/ecosystem/service-workers/devops#locating-and-analyzing-debugging-information)

The Angular service worker exposes debugging information under the `ngsw/` virtual directory. Currently, the single exposed URL is `ngsw/state`. Here is an example of this debug page's contents:

```
NGSW Debug Info:

Driver version: 13.3.7
Driver state: NORMAL ((nominal))
Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c
Last update check: never

=== Version eea7f5f464f90789b621170af5a569d6be077e5c ===

Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65

=== Idle Task Queue ===
Last update tick: 1s496u
Last update run: never
Task queue:

- init post-load (update, cleanup)

Debug log:
```

#### [Driver state](/ecosystem/service-workers/devops#driver-state)

The first line indicates the driver state:

```
Driver state: NORMAL ((nominal))
```

`NORMAL` indicates that the service worker is operating normally and is not in a degraded state.

There are two possible degraded states:

| Degraded states         | Details                                                                                                                                                                                                                                                                                                                                                                                                                       |
| :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `EXISTING_CLIENTS_ONLY` | The service worker does not have a clean copy of the latest known version of the application. Older cached versions are safe to use, so existing tabs continue to run from cache, but new loads of the application will be served from the network. The service worker will try to recover from this state when a new version of the application is detected and installed. This happens when a new `ngsw.json` is available. |
| `SAFE_MODE`             | The service worker cannot guarantee the safety of using cached data. Either an unexpected error occurred or all cached versions are invalid. All traffic will be served from the network, running as little service worker code as possible.                                                                                                                                                                                  |

In both cases, the parenthetical annotation provides the error that caused the service worker to enter the degraded state.

Both states are temporary; they are saved only for the lifetime of the [ServiceWorker instance](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope). The browser sometimes terminates an idle service worker to conserve memory and processor power, and creates a new service worker instance in response to network events. The new instance starts in the `NORMAL` mode, regardless of the state of the previous instance.

#### [Latest manifest hash](/ecosystem/service-workers/devops#latest-manifest-hash)

```
Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c
```

This is the SHA1 hash of the most up-to-date version of the application that the service worker knows about.

#### [Last update check](/ecosystem/service-workers/devops#last-update-check)

```
Last update check: never
```

This indicates the last time the service worker checked for a new version, or update, of the application. `never` indicates that the service worker has never checked for an update.

In this example debug file, the update check is currently scheduled, as explained the next section.

#### [Version](/ecosystem/service-workers/devops#version)

```
=== Version eea7f5f464f90789b621170af5a569d6be077e5c ===

Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65
```

In this example, the service worker has one version of the application cached and being used to serve two different tabs.

**HELPFUL:** This version hash is the "latest manifest hash" listed above. Both clients are on the latest version. Each client is listed by its ID from the `Clients` API in the browser.

#### [Idle task queue](/ecosystem/service-workers/devops#idle-task-queue)

```
=== Idle Task Queue ===
Last update tick: 1s496u
Last update run: never
Task queue:

- init post-load (update, cleanup)
```

The Idle Task Queue is the queue of all pending tasks that happen in the background in the service worker. If there are any tasks in the queue, they are listed with a description. In this example, the service worker has one such task scheduled, a post-initialization operation involving an update check and cleanup of stale caches.

The last update tick/run counters give the time since specific events happened related to the idle queue. The "Last update run" counter shows the last time idle tasks were actually executed. "Last update tick" shows the time since the last event after which the queue might be processed.

#### [Debug log](/ecosystem/service-workers/devops#debug-log)

```
Debug log:
```

Errors that occur within the service worker are logged here.

### [Developer tools](/ecosystem/service-workers/devops#developer-tools)

Browsers such as Chrome provide developer tools for interacting with service workers. Such tools can be powerful when used properly, but there are a few things to keep in mind.

* When using developer tools, the service worker is kept running in the background and never restarts. This can cause behavior with Dev Tools open to differ from behavior a user might experience.

* If you look in the Cache Storage viewer, the cache is frequently out of date. Right-click the Cache Storage title and refresh the caches.

* Stopping and starting the service worker in the Service Worker pane checks for updates

## [Service worker safety](/ecosystem/service-workers/devops#service-worker-safety)

Bugs or broken configurations could cause the Angular service worker to act in unexpected ways. If this happens, the Angular service worker contains several failsafe mechanisms in case an administrator needs to deactivate the service worker quickly.

### [Fail-safe](/ecosystem/service-workers/devops#fail-safe)

To deactivate the service worker, rename the `ngsw.json` file or delete it. When the service worker's request for `ngsw.json` returns a `404`, then the service worker removes all its caches and de-registers itself, essentially self-destructing.

### [Safety worker](/ecosystem/service-workers/devops#safety-worker)

A small script, `safety-worker.js`, is also included in the `@angular/service-worker` npm package. When loaded, it un-registers itself from the browser and removes the service worker caches. This script can be used as a last resort to get rid of unwanted service workers already installed on client pages.

**CRITICAL:** You cannot register this worker directly, as old clients with cached state might not see a new `index.html` which installs the different worker script.

Instead, you must serve the contents of `safety-worker.js` at the URL of the Service Worker script you are trying to unregister. You must continue to do so until you are certain all users have successfully unregistered the old worker. For most sites, this means that you should serve the safety worker at the old Service Worker URL forever. This script can be used to deactivate `@angular/service-worker` and remove the corresponding caches. It also removes any other Service Workers which might have been served in the past on your site.

### [Changing your application's location](/ecosystem/service-workers/devops#changing-your-applications-location)

**IMPORTANT:** Service workers don't work behind redirect. You might have already encountered the error `The script resource is behind a redirect, which is disallowed`.

This can be a problem if you have to change your application's location. If you set up a redirect from the old location, such as `example.com`, to the new location, `www.example.com` in this example, the worker stops working. Also, the redirect won't even trigger for users who are loading the site entirely from Service Worker. The old worker, which was registered at `example.com`, tries to update and sends a request to the old location `example.com`. This request is redirected to the new location `www.example.com` and creates the error: `The script resource is behind a redirect, which is disallowed`.

To remedy this, you might need to deactivate the old worker using one of the preceding techniques: [Fail-safe](/ecosystem/service-workers/devops#fail-safe) or [Safety Worker](/ecosystem/service-workers/devops#safety-worker).

## [More on Angular service workers](/ecosystem/service-workers/devops#more-on-angular-service-workers)

You might also be interested in the following:

----
url: https://angular.dev/errors/NG0912
----

When creating components, Angular generates a unique component ID for each component. This is generated using the Angular component metadata, including but not limited: selectors, the number of host bindings, class property names, view and content queries. When two components metadata are identical (often times sharing the same selector), an ID generation collision will occur.

Component IDs are used in Angular internally:

* for extra annotations of DOM nodes for style encapsulation
* during [hydration](guide/hydration) to restore an application state after [server-side rendering](guide/ssr).

To avoid issues that might be caused by the component ID collision, it's recommended to resolve them as described below.

## [Example of a Component ID collision](#example-of-a-component-id-collision)

```
@Component({
  selector: 'my-component',
  template: 'complex-template',
})
class Some {}

@Component({
  selector: 'my-component',
  template: 'empty-template',
})
class SomeMocked {}
```

Having these two components defined will trigger an ID generation collision and during development a warning will be displayed.

## [Debugging the error](#debugging-the-error)

The warning message includes the class name of the two components whose IDs are colliding.

The problem can be resolved using one of the solutions below:

1. Change the selector of one of the two components. For example by using a pseudo-selector with no effect like `:not()` and a different tag name.

   ```
   @Component({
     selector: 'my-component:not(p)',
     template: 'empty-template',
   })
   class SomeMocked {}
   ```

2. Add an extra host attribute to one of the components.

   ```
   @Component({
     selector: 'my-component',
     template: 'complex-template',
     host: {'some-binding': 'some-value'},
   })
   class Some {}
   ```

----
url: https://angular.dev/api/common/http/HttpSentEvent
----

```
    
      interface HttpSentEvent {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ResponseInit
----

```
    
      type ResponseInit = {  // This is needed as `@types/node` and Undici marks all these properties as readonly. This is not the case in lib.dom.d.ts  -readonly [P in keyof globalThis.ResponseInit]: globalThis.ResponseInit[P];}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/assertInInjectionContext
----

```
function assertInInjectionContext(debugFn: Function): void;
```

@paramdebugFn`Function`

a reference to the function making the assertion (used for the error message).

@returns`void`

Jump to details

----
url: https://angular.dev/api/forms/signals/max
----

```
function max<
  TValue extends number | null,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  maxValue: number | LogicFn<TValue, number | undefined, TPathKind>,
  config?: BaseValidatorConfig<TValue, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@parammaxValue`number | LogicFn<TValue, number | undefined, TPathKind>`

The maximum value, or a LogicFn that returns the maximum value.

@paramconfig`BaseValidatorConfig<TValue, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`maxError()`](/api/forms/signals/maxError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

Jump to details

----
url: https://angular.dev/api/cdk/testing/TestKey
----

```
    
      enum TestKey {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/QueryList
----

```
    
      class QueryList<T> implements Iterable<T> {
}
    
    
```

@param\_emitDistinctChangesOnly`boolean`

@returns`QueryList<T>`

Returns `Observable` of [`QueryList`](/api/core/QueryList) notifying the subscriber of changes.

Returns the QueryList entry at `index`.

@paramindex`number`

@returns`T | undefined`

See [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)

@paramfn`(item: T, index: number, array: T[]) => U`

@returns`U[]`

See [Array.filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)

@parampredicate`(value: T, index: number, array: readonly T[]) => value is S`

@returns`S[]`

@parampredicate`(value: T, index: number, array: readonly T[]) => unknown`

@returns`T[]`

See [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)

@paramfn`(item: T, index: number, array: T[]) => boolean`

@returns`T | undefined`

See [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)

@paramfn`(prevValue: U, curValue: T, curIndex: number, array: T[]) => U`

@paraminit`U`

@returns`U`

See [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)

@paramfn`(item: T, index: number, array: T[]) => void`

@returns`void`

See [Array.some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)

@paramfn`(value: T, index: number, array: T[]) => boolean`

@returns`boolean`

Returns a copy of the internal results list as an Array.

@returns`T[]`

@returns`string`

Updates the stored data of the query list, and resets the `dirty` flag to `false`, so that on change detection, it will not notify of changes to the queries, unless a new change occurs.

@paramresultsTree`(any[] | T)[]`

The query results to store

@paramidentityAccessor`((value: T) => unknown) | undefined`

Optional function for extracting stable object identity from a value in the array. This function is executed for each element of the query result list while comparing current query list with the new one (provided as a first argument of the `reset` function) to detect if the lists are different. If the function is not provided, elements are compared as is (without any pre-processing).

@returns`void`

Triggers a change event by emitting on the `changes` [`EventEmitter`](/api/core/EventEmitter).

@returns`void`

internal

@returns`void`

internal

@returns`void`

## [Description](#description)

An unmodifiable list of items that Angular keeps up to date when the state of the application changes.

The type of object that [`ViewChildren`](/api/core/ViewChildren), [`ContentChildren`](/api/core/ContentChildren), and [`QueryList`](/api/core/QueryList) provide.

Implements an iterable interface, therefore it can be used in both ES6 javascript `for (var i of items)` loops as well as in Angular templates with `@for(i of myList; track $index)`.

Changes can be observed by subscribing to the `changes` `Observable`. \*

## [Usage Notes](#usage-notes)

### [Example](#example)

```
@Component({...})
class Container {
  @ViewChildren(Item) items:QueryList<Item>;
}
```

Jump to details

----
url: https://angular.dev/api/core/KeyValueDifferFactory
----

```
    
      interface KeyValueDifferFactory {}
    
    
```

Test to see if the differ knows how to diff this kind of object.

@paramobjects`any`

@returns`boolean`

Create a [`KeyValueDiffer`](/api/core/KeyValueDiffer).

@returns`KeyValueDiffer<K, V>`

Jump to details

----
url: https://angular.dev/api/forms/signals/form
----

```
function form<TModel>(model: WritableSignal<TModel>): FieldTree<TModel>;
function form<TModel>(
  model: WritableSignal<TModel>,
  schemaOrOptions: SchemaOrSchemaFn<TModel> | FormOptions<TModel>,
): FieldTree<TModel>;
function form<TModel>(
  model: WritableSignal<TModel>,
  schema: SchemaOrSchemaFn<TModel>,
  options: FormOptions<TModel>,
): FieldTree<TModel>;
```

Creates a form wrapped around the given model data. A form is represented as simply a [`FieldTree`](/api/forms/signals/FieldTree) of the model data.

`form` uses the given model as the source of truth and *does not* maintain its own copy of the data. This means that updating the value on a [`FieldState`](/api/forms/signals/FieldState) updates the originally passed in model as well.

@parammodel`WritableSignal<TModel>`

A writable signal that contains the model data for the form. The resulting field structure will match the shape of the model and any changes to the form data will be written to the model.

@returns`FieldTree<TModel>`

Usage notes

```
const nameModel = signal({first: '', last: ''});
const nameForm = form(nameModel);
nameForm.first().value.set('John');
nameForm().value(); // {first: 'John', last: ''}
nameModel(); // {first: 'John', last: ''}
```

Creates a form wrapped around the given model data. A form is represented as simply a [`FieldTree`](/api/forms/signals/FieldTree) of the model data.

`form` uses the given model as the source of truth and *does not* maintain its own copy of the data. This means that updating the value on a [`FieldState`](/api/forms/signals/FieldState) updates the originally passed in model as well.

@parammodel`WritableSignal<TModel>`

A writable signal that contains the model data for the form. The resulting field structure will match the shape of the model and any changes to the form data will be written to the model.

@paramschemaOrOptions`SchemaOrSchemaFn<TModel> | FormOptions<TModel>`

The second argument can be either

1. A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.). When passing a schema, the form options can be passed as a third argument if needed.
2. The form options

@returns`FieldTree<TModel>`

Usage notes

```
const nameModel = signal({first: '', last: ''});
const nameForm = form(nameModel);
nameForm.first().value.set('John');
nameForm().value(); // {first: 'John', last: ''}
nameModel(); // {first: 'John', last: ''}
```

The form can also be created with a schema, which is a set of rules that define the logic for the form. The schema can be either a pre-defined schema created with the [`schema`](/api/forms/signals/schema) function, or a function that builds the schema by binding logic to a parts of the field structure.

```
const nameForm = form(signal({first: '', last: ''}), (name) => {
  required(name.first);
  pattern(name.last, /^[a-z]+$/i, {message: 'Alphabet characters only'});
});
nameForm().valid(); // false
nameForm().value.set({first: 'John', last: 'Doe'});
nameForm().valid(); // true
```

Creates a form wrapped around the given model data. A form is represented as simply a [`FieldTree`](/api/forms/signals/FieldTree) of the model data.

`form` uses the given model as the source of truth and *does not* maintain its own copy of the data. This means that updating the value on a [`FieldState`](/api/forms/signals/FieldState) updates the originally passed in model as well.

@parammodel`WritableSignal<TModel>`

A writable signal that contains the model data for the form. The resulting field structure will match the shape of the model and any changes to the form data will be written to the model.

@paramschema`SchemaOrSchemaFn<TModel>`

A schema or a function used to specify logic for the form (e.g. validation, disabled fields, etc.)

@paramoptions`FormOptions<TModel>`

The form options

@returns`FieldTree<TModel>`

Usage notes

```
const nameModel = signal({first: '', last: ''});
const nameForm = form(nameModel);
nameForm.first().value.set('John');
nameForm().value(); // {first: 'John', last: ''}
nameModel(); // {first: 'John', last: ''}
```

The form can also be created with a schema, which is a set of rules that define the logic for the form. The schema can be either a pre-defined schema created with the [`schema`](/api/forms/signals/schema) function, or a function that builds the schema by binding logic to a parts of the field structure.

```
const nameForm = form(signal({first: '', last: ''}), (name) => {
  required(name.first);
  validate(name.last, ({value}) => !/^[a-z]+$/i.test(value()) ? {kind: 'alphabet-only'} : undefined);
});
nameForm().valid(); // false
nameForm().value.set({first: 'John', last: 'Doe'});
nameForm().valid(); // true
```

## [Description](#description)

Creates a form wrapped around the given model data. A form is represented as simply a [`FieldTree`](/api/forms/signals/FieldTree) of the model data.

`form` uses the given model as the source of truth and *does not* maintain its own copy of the data. This means that updating the value on a [`FieldState`](/api/forms/signals/FieldState) updates the originally passed in model as well.

## [Usage Notes](#usage-notes)

```
const nameModel = signal({first: '', last: ''});
const nameForm = form(nameModel);
nameForm.first().value.set('John');
nameForm().value(); // {first: 'John', last: ''}
nameModel(); // {first: 'John', last: ''}
```

Jump to details

----
url: https://angular.dev/api/forms/signals/minError
----

```
function minError(
  min: number,
  options: WithFieldTree<ValidationErrorOptions>,
): MinValidationError;
function minError(
  min: number,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<MinValidationError>;
```

Create a min value error associated with the target field

@parammin`number`

The min value constraint

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`MinValidationError`

Create a min value error

@parammin`number`

The min value constraint

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<MinValidationError>`

Jump to details

----
url: https://angular.dev/api/router/RouterLinkWithHref
----

## [API](/api/router/RouterLinkWithHref#api)

```
    
      class RouterLinkWithHref implements OnChanges ,OnDestroy {}
    
    
```

## [Description](/api/router/RouterLinkWithHref#description)

When applied to an element in a template, makes that element a link that initiates navigation to a route. Navigation opens one or more routed components in one or more `<router-outlet>` locations on the page.

Given a route configuration `[{ path: 'user/:name', component: UserCmp }]`, the following creates a static link to the route: `<a routerLink="/user/bob">link to user component</a>`

You can use dynamic values to generate the link. For a dynamic link, pass an array of path segments, followed by the params for each segment. For example, `['/team', teamId, 'user', userName, {details: true}]` generates a link to `/team/11/user/bob;details=true`.

Multiple static segments can be merged into one term and combined with dynamic segments. For example, `['/team/11/user', userName, {details: true}]`

The input that you provide to the link is treated as a delta to the current URL. For instance, suppose the current URL is `/user/(box//aux:team)`. The link `<a [routerLink]="['/user/jim']">Jim</a>` creates the URL `/user/(jim//aux:team)`. See [`Router#createUrlTree`](/api/router/Router#createUrlTree) for more information.

## [Exported by](/api/router/RouterLinkWithHref#exported-by)

* `RouterModule`

## [Usage Notes](/api/router/RouterLinkWithHref#usage-notes)

You can use absolute or relative paths in a link, set query parameters, control how parameters are handled, and keep a history of navigation states.

### [Relative link paths](/api/router/RouterLinkWithHref#relative-link-paths)

The first segment name can be prepended with `/`, `./`, or `../`.

* If the first segment begins with `/`, the router looks up the route from the root of the app.
* If the first segment begins with `./`, or doesn't begin with a slash, the router looks in the children of the current activated route.
* If the first segment begins with `../`, the router goes up one level in the route tree.

### [Setting and handling query params and fragments](/api/router/RouterLinkWithHref#setting-and-handling-query-params-and-fragments)

The following link adds a query parameter and a fragment to the generated URL:

```
<a [routerLink]="['/user/bob']" [queryParams]="{debug: true}"
fragment="education"> link to user component
</a>
```

By default, the directive constructs the new URL using the given query parameters. The example generates the link: `/user/bob?debug=true#education`.

You can instruct the directive to handle query parameters differently by specifying the `queryParamsHandling` option in the link. Allowed values are:

* `'merge'`: Merge the given `queryParams` into the current query params.
* `'preserve'`: Preserve the current query params.

For example:

```
<a [routerLink]="['/user/bob']" [queryParams]="{debug: true}"
queryParamsHandling="merge"> link to user component
</a>
```

`queryParams`, `fragment`, `queryParamsHandling`, `preserveFragment`, and `relativeTo` cannot be used when the `routerLink` input is a [`UrlTree`](/api/router/UrlTree).

See [`UrlCreationOptions#queryParamsHandling`](/api/router/UrlCreationOptions#queryParamsHandling).

### [Preserving navigation history](/api/router/RouterLinkWithHref#preserving-navigation-history)

You can provide a `state` value to be persisted to the browser's [`History.state` property](https://developer.mozilla.org/en-US/docs/Web/API/History#Properties). For example:

```
<a [routerLink]="['/user/bob']" [state]="{tracingId: 123}">
  link to user component
</a>
```

Use [`Router#currentNavigation`](/api/router/Router#currentNavigation) to retrieve a saved Signal navigation-state value. For example, to capture the `tracingId` during the [`NavigationStart`](/api/router/NavigationStart) event:

```
// Get NavigationStart events
router.events.pipe(filter(e => e instanceof NavigationStart)).subscribe(e => {
  const navigation = router.currentNavigation();
  tracingService.trace({id: navigation.extras.state.tracingId});
});
```

### [RouterLink compatible custom elements](/api/router/RouterLinkWithHref#routerlink-compatible-custom-elements)

In order to make a custom element work with routerLink, the corresponding custom element must implement the `href` attribute and must list `href` in the array of the static property/getter `observedAttributes`.

Jump to details

----
url: https://angular.dev/api/router/Routes
----

```
    
      type Routes = Route[]
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/testing/HttpClientTestingModule
----

* * [HttpClientTestingModule](/api/common/http/testing/HttpClientTestingModule)
  * [HttpTestingController](/api/common/http/testing/HttpTestingController)
  * [provideHttpClientTesting](/api/common/http/testing/provideHttpClientTesting)
  * [RequestMatch](/api/common/http/testing/RequestMatch)
  * [TestRequest](/api/common/http/testing/TestRequest)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

Deprecation warning

Add [`provideHttpClientTesting()`](/api/common/http/testing/provideHttpClientTesting) to your providers instead.

## [API](#api)

```
    
      class HttpClientTestingModule {}
    
    
```

## [Description](#description)

Configures `HttpClientTestingBackend` as the `HttpBackend` used by `HttpClient`.

Inject [`HttpTestingController`](/api/common/http/testing/HttpTestingController) to expect and flush requests in your tests.

Jump to details

----
url: https://angular.dev/api/router/defaultUrlMatcher
----

## [API](/api/router/defaultUrlMatcher#api)

```
function defaultUrlMatcher(
  segments: UrlSegment[],
  segmentGroup: UrlSegmentGroup,
  route: Route,
): UrlMatchResult | null;
```

@paramsegments`UrlSegment[]`

The remaining unmatched segments in the current navigation

@paramsegmentGroup`UrlSegmentGroup`

The current segment group being matched

@paramroute`Route`

The [`Route`](/api/router/Route) to match against.

@returns`UrlMatchResult | null`

The resulting match information or `null` if the `route` should not match.

## [Description](/api/router/defaultUrlMatcher#description)

Matches the route configuration (`route`) against the actual URL (`segments`).

When no matcher is defined on a [`Route`](/api/router/Route), this is the matcher used by the Router by default.

Jump to details

----
url: https://angular.dev/api/common/getLocaleFirstDayOfWeek
----

Angular recommends relying on the `Intl` API for i18n. Intl's [`getWeekInfo`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo) has partial support (Chromium M99 & Safari 17). You may want to rely on the following alternatives:

* Libraries like [`Luxon`](https://moment.github.io/luxon/#/) rely on `Intl` but fallback on the ISO 8601 definition (monday) if `getWeekInfo` is not supported.
* Other librairies like [`date-fns`](https://date-fns.org/), [`day.js`](https://day.js.org/en/) or [`weekstart`](https://www.npmjs.com/package/weekstart) library provide their own locale based data for the first day of the week.

## [API](#api)

```
function getLocaleFirstDayOfWeek(locale: string): WeekDay;
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Intl's [`getWeekInfo`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getWeekInfo) has partial support (Chromium M99 & Safari 17). You may want to rely on the following alternatives:

* Libraries like [`Luxon`](https://moment.github.io/luxon/#/) rely on `Intl` but fallback on the ISO 8601 definition (monday) if `getWeekInfo` is not supported.
* Other librairies like [`date-fns`](https://date-fns.org/), [`day.js`](https://day.js.org/en/) or [`weekstart`](https://www.npmjs.com/package/weekstart) library provide their own locale based data for the first day of the week.

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`WeekDay`

A day index number, using the 0-based week-day index for `en-US` (Sunday = 0, Monday = 1, ...). For example, for `fr-FR`, returns 1 to indicate that the first day is Monday.

Jump to details

----
url: https://angular.dev/api/common/getLocaleDayNames
----

```
function getLocaleDayNames(
  locale: string,
  formStyle: FormStyle,
  width: TranslationWidth,
): readonly string[];
```

An array of localized name strings. For example,`[Sunday, Monday, ... Saturday]` for `en-US`.

Jump to details

----
url: https://angular.dev/api/forms/signals/createLimitSelectionKey
----

```
function createLimitSelectionKey(): LimitSelectionKey;
```

@returns`LimitSelectionKey`

Jump to details

----
url: https://angular.dev/api/forms/signals/maxLengthError
----

```
function maxLengthError(
  maxLength: number,
  options: WithFieldTree<ValidationErrorOptions>,
): MaxLengthValidationError;
function maxLengthError(
  maxLength: number,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<MaxLengthValidationError>;
```

Create a maxLength error associated with the target field

@parammaxLength`number`

The maxLength constraint

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`MaxLengthValidationError`

Create a maxLength error

@parammaxLength`number`

The maxLength constraint

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<MaxLengthValidationError>`

Jump to details

----
url: https://angular.dev/api/common/getNumberOfCurrencyDigits
----

Angular recommends relying on the `Intl` API for i18n. This function should not be used anymore. Let `Intl.NumberFormat` determine the number of digits to display for the currency

## [API](#api)

```
function getNumberOfCurrencyDigits(code: string): number;
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. This function should not be used anymore. Let `Intl.NumberFormat` determine the number of digits to display for the currency

@paramcode`string`

The currency code.

@returns`number`

The number of decimal digits, typically 0 or 2.

Jump to details

----
url: https://angular.dev/api/core/ViewEncapsulation
----

```
    
      enum ViewEncapsulation {}
    
    
```

Emulates a native Shadow DOM encapsulation behavior by adding a specific attribute to the component's host element and applying the same attribute to all the CSS selectors provided via [`styles`](/api/core/Component#styles) or [`styleUrls`](/api/core/Component#styleUrls).

This is the default option.

Doesn't provide any sort of CSS style encapsulation, meaning that all the styles provided via [`styles`](/api/core/Component#styles) or [`styleUrls`](/api/core/Component#styleUrls) are applicable to any HTML element of the application regardless of their host Component.

Uses the browser's native Shadow DOM API to encapsulate CSS styles, meaning that it creates a ShadowRoot for the component's host element which is then used to encapsulate all the Component's styling.

@experimental

Similar to `ShadowDom`, but prevents any external styles from leaking into the component's ShadowRoot. This is useful when you want to ensure that the component's styles are completely isolated from the rest of the application, including global styles.

## [Description](#description)

Defines the CSS styles encapsulation policies for the [`Component`](/api/core/Component) decorator's `encapsulation` option.

See [`encapsulation`](/api/core/Component#encapsulation).

Jump to details

----
url: https://angular.dev/api/service-worker/NoNewVersionDetectedEvent
----

```
    
      interface NoNewVersionDetectedEvent {}
    
    
```

Jump to details

----
url: https://angular.dev/api/ssr/ServerRenderingOptions
----

```
    
      interface ServerRenderingOptions {}
    
    
```

The maximum allowed response body size when using the Fetch API. @default 1MB

Jump to details

----
url: https://angular.dev/guide/zoneless
----

## [Why use Zoneless?](#why-use-zoneless)

The main advantages to removing ZoneJS as a dependency are:

* **Improved performance**: ZoneJS uses DOM events and async tasks as indicators of when application state *might* have updated and subsequently triggers application synchronization to run change detection on the application's views. ZoneJS does not have any insight into whether application state actually changed and so this synchronization is triggered more frequently than necessary.
* **Improved Core Web Vitals**: ZoneJS brings a fair amount of overhead, both in payload size and startup time.
* **Improved debugging experience**: ZoneJS makes debugging code more difficult. Stack traces are harder to understand with ZoneJS. It's also difficult to understand when code breaks as a result of being outside the Angular Zone.
* **Better ecosystem compatibility**: ZoneJS works by patching browser APIs but does not automatically have patches for every new browser API. Some APIs, such as `async`/`await`, cannot be patched effectively and must be downleveled to work with ZoneJS. Sometimes libraries in the ecosystem are also incompatible with the way ZoneJS patches the native APIs. Removing ZoneJS as a dependency ensures better long-term compatibility by removing a source of complexity, monkey patching, and ongoing maintenance.

## [Enabling Zoneless in an application](#enabling-zoneless-in-an-application)

Zoneless is the default in Angular v21+ so you do not need to do anything to enable it. You should verify that [`provideZoneChangeDetection`](/api/core/provideZoneChangeDetection) is not used anywhere to override the default configuration.

If you are using Angular v20, enable zoneless change detection by adding [`provideZonelessChangeDetection()`](/api/core/provideZonelessChangeDetection) at bootstrap:

### standalone bootstrap

```
bootstrapApplication(MyApp, {providers: [provideZonelessChangeDetection()]});
```

### NgModule bootstrap

```
platformBrowser().bootstrapModule(AppModule);

@NgModule({
  providers: [provideZonelessChangeDetection()],
})
export class AppModule {}
```

## [Removing ZoneJS](#removing-zonejs)

Zoneless applications should remove ZoneJS entirely from the build to reduce bundle size. ZoneJS is typically loaded via the `polyfills` option in `angular.json`, both in the `build` and `test` targets. Remove `zone.js` and `zone.js/testing` from both to remove it from the build. Projects which use an explicit `polyfills.ts` file should remove `import 'zone.js';` and `import 'zone.js/testing';` from the file.

After removing ZoneJS from the build, there is no longer a need for a `zone.js` dependency either and the package can be removed entirely:

```
npm uninstall zone.js
```

## [Requirements for Zoneless compatibility](#requirements-for-zoneless-compatibility)

Angular relies on notifications from core APIs in order to determine when to run change detection and on which views. These notifications include:

* [`ChangeDetectorRef.markForCheck`](/api/core/ChangeDetectorRef#markForCheck) (called automatically by [`AsyncPipe`](/api/common/AsyncPipe))
* [`ComponentRef.setInput`](/api/core/ComponentRef#setInput)
* Updating a signal that's read in a template
* Bound host or template listeners callbacks
* Attaching a view that was marked dirty by one of the above

### [`OnPush`-compatible components](#onpush-compatible-components)

One way to ensure that a component is using the correct notification mechanisms from above is to use [ChangeDetectionStrategy.OnPush](/best-practices/skipping-subtrees#using-onpush).

The `OnPush` change detection strategy is not required, but it is a recommended step towards zoneless compatibility for application components. It is not always possible for library components to use [`ChangeDetectionStrategy.OnPush`](/api/core/ChangeDetectionStrategy#OnPush). When a library component is a host for user-components which might use [`ChangeDetectionStrategy.Eager`](/api/core/ChangeDetectionStrategy#Eager)/`Default`, it cannot use `OnPush` because that would prevent the child component from being refreshed if it is not `OnPush` compatible and relies on ZoneJS to trigger change detection. Components can use the `Default` strategy as long as they notify Angular when change detection needs to run (calling `markForCheck`, using signals, [`AsyncPipe`](/api/common/AsyncPipe), etc.). Being a host for a user component means using an API such as [`ViewContainerRef.createComponent`](/api/core/ViewContainerRef#createComponent) and not just hosting a portion of a template from a user component (i.e. content projection or using a template ref input).

### [Remove `NgZone.onMicrotaskEmpty`, `NgZone.onUnstable`, `NgZone.isStable`, or `NgZone.onStable`](#remove-ngzoneonmicrotaskempty-ngzoneonunstable-ngzoneisstable-or-ngzoneonstable)

Applications and libraries need to remove uses of [`NgZone.onMicrotaskEmpty`](/api/core/NgZone#onMicrotaskEmpty), [`NgZone.onUnstable`](/api/core/NgZone#onUnstable), and [`NgZone.onStable`](/api/core/NgZone#onStable). These observables will never emit when an Application enables zoneless change detection. Similarly, [`NgZone.isStable`](/api/core/NgZone#isStable) will always be `true` and should not be used as a condition for code execution.

The [`NgZone.onMicrotaskEmpty`](/api/core/NgZone#onMicrotaskEmpty) and [`NgZone.onStable`](/api/core/NgZone#onStable) observables are most often used to wait for Angular to complete change detection before performing a task. Instead, these can be replaced by [`afterNextRender`](/api/core/afterNextRender) if they need to wait for a single change detection or [`afterEveryRender`](/api/core/afterEveryRender) if there is some condition that might span several change detection rounds. In other cases, these observables were used because they happened to be familiar and have similar timing to what was needed. More straightforward or direct DOM APIs can be used instead, such as `MutationObserver` when code needs to wait for certain DOM state (rather than waiting for it indirectly through Angular's render hooks).

### NgZone.run and NgZone.runOutsideAngular are compatible with Zoneless

[`NgZone.run`](/api/core/NgZone#run) and [`NgZone.runOutsideAngular`](/api/core/NgZone#runOutsideAngular) do not need to be removed in order for code to be compatible with Zoneless applications. In fact, removing these calls can lead to performance regressions for libraries that are used in applications that still rely on ZoneJS.

### [`PendingTasks` for Server Side Rendering (SSR)](#pendingtasks-for-server-side-rendering-ssr)

If you are using SSR with Angular, you may know that it relies on ZoneJS to help determine when the application is "stable" and can be serialized. If there are asynchronous tasks that should prevent serialization, an application not using ZoneJS must make Angular aware of these with the [PendingTasks](/api/core/PendingTasks) service. Serialization will wait for the first moment that all pending tasks have been removed.

The two most straightforward uses of pending tasks are the `run` method:

```
const taskService = inject(PendingTasks);
taskService.run(async () => {
  const someResult = await doSomeWorkThatNeedsToBeRendered();
  this.someState.set(someResult);
});
```

For more complicated use-cases, you can manually add and remove a pending task:

```
const taskService = inject(PendingTasks);
const taskCleanup = taskService.add();
try {
  await doSomeWorkThatNeedsToBeRendered();
} catch {
  // handle error
} finally {
  taskCleanup();
}
```

In addition, the [pendingUntilEvent](/api/core/rxjs-interop/pendingUntilEvent) helper in `rxjs-interop` ensures the application remains unstable until the observable emits, completes, errors, or is unsubscribed.

```
readonly myObservableState = someObservable.pipe(pendingUntilEvent());
```

The framework uses this service internally as well to prevent serialization until asynchronous tasks are complete. These include, but are not limited to, an ongoing Router navigation and an incomplete [`HttpClient`](/api/common/http/HttpClient) request.

### [Reactive forms in zoneless applications](#reactive-forms-in-zoneless-applications)

Reactive forms model updates (`setValue`, `patchValue`, [`FormArray.push`](/api/forms/FormArray#push), and similar APIs) update form state and emit form observables, but they do not automatically schedule component change detection.

If a template depends on reactive forms state, connect forms observables to a change-detection notification (for example [`ChangeDetectorRef.markForCheck()`](/api/core/ChangeDetectorRef#markForCheck)), or reflect the data through signals consumed by the template.

## [Testing and Debugging](#testing-and-debugging)

### [Using Zoneless in `TestBed`](#using-zoneless-in-testbed)

[`TestBed`](/api/core/testing/TestBed) uses zoneless change detection by default, even when `zone.js` is loaded via the `polyfills`.

To use Zone-based change detection in a test, add [`provideZoneChangeDetection()`](/api/core/provideZoneChangeDetection) to the [`TestBed`](/api/core/testing/TestBed) providers. Adding [`provideZonelessChangeDetection()`](/api/core/provideZonelessChangeDetection) is optional:

```
TestBed.configureTestingModule({
  // Optional: include the provider to force the testing environment
  // uses the same zoneless behavior as a zoneless application.
  providers: [provideZonelessChangeDetection()],
});

const fixture = TestBed.createComponent(MyComponent);
await fixture.whenStable();
```

To ensure tests have the most similar behavior to production code, avoid using `fixture.detectChanges()` when possible. This forces change detection to run when Angular might otherwise have not scheduled change detection. Tests should ensure these notifications are happening and allow Angular to handle when to synchronize state rather than manually forcing it to happen in the test.

For existing test suites, using `fixture.detectChanges()` is a common pattern and it is likely not worth the effort of converting these to `await fixture.whenStable()`. [`TestBed`](/api/core/testing/TestBed) will still enforce that the fixture's component is `OnPush` compatible and throws `ExpressionChangedAfterItHasBeenCheckedError` if it finds that template values were updated without a change notification (i.e. `fixture.componentInstance.someValue = 'newValue';`). If the component is used in production, this issue should be addressed by updating the component to use signals for state or call [`ChangeDetectorRef.markForCheck()`](/api/core/ChangeDetectorRef#markForCheck). If the component is only used as a test wrapper and never used in an application, it is acceptable to use [`ChangeDetectionStrategy.Eager`](/api/core/ChangeDetectionStrategy#Eager) for it and call `fixture.changeDetectorRef.markForCheck()`.

**IMPORTANT:** `fixture.changeDetectorRef` belongs to the fixture's host view, so its `markForCheck()` doesn't refresh the template of an `OnPush` component. Your application components shouldn't need it in tests. If a test needs it, the component usually isn't fully reactive or [zoneless compatible](#requirements-for-zoneless-compatibility).

### [Debug-mode check to ensure updates are detected](#debug-mode-check-to-ensure-updates-are-detected)

Angular also provides an additional tool to help verify that an application is making updates to state in a zoneless-compatible way. `provideCheckNoChangesConfig({exhaustive: true, interval: <milliseconds>})` can be used to periodically check to ensure that no bindings have been updated without a notification. Angular throws `ExpressionChangedAfterItHasBeenCheckedError` if there is an updated binding that would not have refreshed by the zoneless change detection.

----
url: https://angular.dev/update
----

Just like Web and the entire web ecosystem, Angular is continuously improving. Angular balances continuous improvement with a strong focus on stability and making updates straightforward. Keeping your Angular application up-to-date enables you to take advantage of leading-edge new features, as well as optimizations and bug fixes.

This document contains information and resources to help you keep your Angular applications and libraries up-to-date.

For information about our versioning policy and practices — including support and deprecation practices, as well as the release schedule — see [Angular versioning and releases](reference/releases "Angular versioning and releases").

**HELPFUL:** If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade "Upgrading from AngularJS"). *AngularJS* is the name for all v1.x versions of Angular.

## [Getting notified of new releases](#getting-notified-of-new-releases)

To be notified when new releases are available, follow [@angular](https://x.com/angular "@angular on X") on X (formerly Twitter) or subscribe to the [Angular blog](https://blog.angular.dev "Angular blog").

## [Learning about new features](#learning-about-new-features)

What's new? What's changed? We share the most important things you need to know on the Angular blog in [release announcements](https://blog.angular.dev/ "Angular blog - release announcements").

To review a complete list of changes, organized by version, see the [Angular change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log").

## [Checking your version of Angular](#checking-your-version-of-angular)

To check your application's version of Angular use the `ng version` command from within your project directory.

## [Finding the current version of Angular](#finding-the-current-version-of-angular)

The most recent stable released version of Angular appears [on npm](https://www.npmjs.com/package/@angular/core "Angular on npm") under "Version." For example, `16.2.4`.

You can also find the most current version of Angular by using the CLI command [`ng update`](cli/update). By default, [`ng update`](cli/update) (without additional arguments) lists the updates that are available to you.

## [Updating your environment and apps](#updating-your-environment-and-apps)

To make updating uncomplicated, we provide complete instructions in the interactive [Angular Update Guide](update-guide).

The Angular Update Guide provides customized update instructions, based on the current and target versions that you specify. It includes basic and advanced update paths, to match the complexity of your applications. It also includes troubleshooting information and any recommended manual changes to help you get the most out of the new release.

For simple updates, the CLI command [`ng update`](cli/update) is all you need. Without additional arguments, [`ng update`](cli/update) lists the updates that are available to you and provides recommended steps to update your application to the most current version.

[Angular Versioning and Releases](reference/releases#angular-versioning "Angular Release Practices, Versioning") describes the level of change that you can expect based on a release's version number. It also describes supported update paths.

## [Resource summary](#resource-summary)

* Release announcements: [Angular blog - release announcements](https://blog.angular.dev/ "Angular blog announcements about recent releases")

* Release details: [Angular change log](https://github.com/angular/angular/blob/main/CHANGELOG.md "Angular change log")

* Update instructions: [Angular Update Guide](update-guide)

* Update command reference: [Angular CLI `ng update` command reference](cli/update)

* Versioning, release, support, and deprecation practices: [Angular versioning and releases](reference/releases "Angular versioning and releases")

----
url: https://angular.dev/api/core/IterableChangeRecord
----

```
    
      interface IterableChangeRecord<V> {}
    
    
```

Current index of the item in `Iterable` or null if removed.

Previous index of the item in `Iterable` or null if added.

The item.

Track by identity as computed by the [`TrackByFunction`](/api/core/TrackByFunction).

Jump to details

----
url: https://angular.dev/api/core/Directive
----

```
@Directive({
  selector?: string | undefined;
  inputs?:
    | (
        | string
        | {
            name: string;
            alias?: string | undefined;
            required?: boolean | undefined;
            transform?: ((value: any) => any) | undefined;
          }
      )[]
    | undefined;
  outputs?: string[] | undefined;
  providers?: Provider[] | undefined;
  exportAs?: string | undefined;
  queries?: { [key: string]: any } | undefined;
  host?: { [key: string]: string } | undefined;
  jit?: true | undefined;
  standalone?: boolean | undefined;
  hostDirectives?:
    | (
        | Type<unknown>
        | {
            directive: Type<unknown>;
            inputs?: string[] | undefined;
            outputs?: string[] | undefined;
          }
      )[]
    | undefined;
})
```

```
<form>
  <input type="text">
  <input type="radio">
<form>
```

Set `standalone` to `false` if you want to import the directive into an NgModule.

Standalone directives that should be applied to the host whenever the directive is matched. By default, none of the inputs or outputs of the host directives will be available on the host, unless they are specified in the `inputs` or `outputs` properties.

You can additionally alias inputs and outputs by putting a colon and the alias after the original input or output name. For example, if a directive applied via `hostDirectives` defines an input named `menuDisabled`, you can alias this to `disabled` by adding `'menuDisabled: disabled'` as an entry to `inputs`.

## [Description](#description)

Decorator that marks a class as an Angular directive. You can define your own directives to attach custom behavior to elements in the DOM.

The options provide configuration metadata that determines how the directive should be processed, instantiated and used at runtime.

Directive classes, like component classes, can implement [life-cycle hooks](guide/components/lifecycle) to influence their configuration and behavior.

## [Usage Notes](#usage-notes)

To define a directive, mark the class with the decorator and provide metadata.

```
import {Directive} from '@angular/core';

@Directive({
  selector: 'my-directive',
})
export class MyDirective {
...
}
```

### [Declaring directives](#declaring-directives)

By default, directives are marked as standalone, which makes them available to other components in your application.

```
@Directive({
  selector: 'my-directive',
})
```

Please make sure that directives marked as standalone are not already declared in an NgModule.

\*\* Declaring a directive in an NgModule \*\* If you want to declare a directive in an ngModule, add the `standalone: false` flag to the Directive decorator metadata and add the directive to the `declarations` and `exports` fields of your ngModule.

```
@Directive({
  standalone: false,
  selector: 'my-directive',
})
class MyDirective {}

@NgModule({
  declarations: [MyDirective, SomeComponent],
  exports: [MyDirective], // making it available outside of this module
})
class SomeNgModule {}
```

When declaring a directive in an NgModule, please make sure that:

* the directive is declared in exactly one NgModule.
* the directive is not standalone.
* you do not re-declare a directive imported from another module.
* the directive is included into the `exports` field as well if you want this directive to be accessible for components outside of the NgModule.

Jump to details

----
url: https://angular.dev/api/forms/signals/Subfields
----

```
    
      type Subfields<TModel, TMode extends 'writable' | 'readonly' = 'writable'> = {  readonly [K in keyof TModel as TModel[K] extends Function ? never : K]: MaybeFieldTree<    TModel[K],    string,    TMode  >;} & {  [Symbol.iterator](): Iterator<[string, MaybeFieldTree<TModel[keyof TModel], string, TMode>]>;}
    
    
```

Jump to details

----
url: https://angular.dev/ai
----

Build with AI

# Build with AI

[](https://github.com/angular/angular/edit/main/adev/src/content/ai/overview.md "Edit this page")

Build AI-powered apps. Develop faster with AI.

**HELPFUL:** Looking to get started with building in your favorite AI powered IDE?\
Check out our [prompt rules and best practices](/ai/develop-with-ai).

Generative AI (GenAI) with large language models (LLMs) enables the creation of sophisticated and engaging application experiences, including personalized content, intelligent recommendations, media generation and comprehension, information summarization, and dynamic functionality.

Developing features like these would have previously required deep domain expertise and significant engineering effort. However, new products and SDKs are lowering the barrier to entry. Angular is well-suited for integrating AI into your web application as a result of:

* Angular's robust templating APIs enable the creation of dynamic, cleanly composed UIs made from generated content
* Strong, signal-based architecture designed to dynamically manage data and state
* Angular integrates seamlessly with AI SDKs and APIs

This guide demonstrates how you can use [Genkit](/ai#build-ai-powered-applications-with-genkit-and-angular), [Firebase AI Logic](/ai#build-ai-powered-applications-with-firebase-ai-logic-and-angular), and the [Gemini API](/ai#build-ai-powered-applications-with-gemini-api-and-angular) to infuse your Angular apps with AI today. This guide will jumpstart your AI-powered web app development journey by explaining how to begin integrating AI into Angular apps. This guide also shares resources, such as starter kits, example code, and recipes for common workflows, you can use to get up to speed quickly.

To get started, you should have a basic understanding of Angular. New to Angular? Try our [essentials guide](/essentials) or our [getting started tutorials](/tutorials).

**NOTE:** While this page features integrations and examples with Google AI products, tools like Genkit are model agnostic and allow you to choose your own model. In many cases, the examples and code samples are applicable to other third-party solutions.

## [Getting Started](#getting-started)

Building AI-powered applications is a new and rapidly developing field. It can be challenging to decide where to start and which technologies to choose. The following section provides three options to choose from:

1. *Genkit* gives you the choice of [supported model and interface with a unified API](https://genkit.dev) for building full-stack applications. Ideal for applications requiring sophisticated back-end AI logic, such as personalized recommendations.

2. *Firebase AI Logic* provides a secure client-side API for Google's models to build client-side only applications or mobile apps. Best for interactive AI features directly in the browser, such as real-time text analysis or basic chatbots.

3. *Gemini API* enables you to build an application that uses the methods and functionality exposed through the API surface directly, best for full-stack applications. Suitable for applications needing direct control over AI models, like custom image generation or deep data processing.

### [Build AI-powered applications with Genkit and Angular](#build-ai-powered-applications-with-genkit-and-angular)

[Genkit](https://genkit.dev) is an open-source toolkit designed to help you build AI-powered features in web and mobile apps. It offers a unified interface for integrating AI models from Google, OpenAI, Anthropic, Ollama, and more, so you can explore and choose the best models for your needs. As a server-side solution, your web apps need a supported server environment, such as a node-based server in order to integrate with Genkit. Building a full-stack app using Angular SSR gives you the starting server-side code, for example.

Here are examples of how to build with Genkit and Angular:

* [Agentic Apps with Genkit and Angular starter-kit](https://github.com/angular/examples/tree/main/genkit-angular-starter-kit) — New to building with AI? Start here with a basic app that features an agentic workflow. Perfect place to start for your first AI building experience.

* [Use Genkit in an Angular app](https://genkit.dev/docs/frameworks/angular/) — Build a basic application that uses Genkit Flows, Angular and Gemini 2.5 Flash. This step-by-step walkthrough guides you through creating a full-stack Angular application with AI features.

* [Dynamic Story Generator app](https://github.com/angular/examples/tree/main/genkit-angular-story-generator) — Learn to build an agentic Angular app powered by Genkit, Gemini and Imagen 3 to dynamically generate a story based on user interaction featuring beautiful image panels to accompany the events that take place. Start here if you'd like to experiment with a more advanced use-case.

  This example also has an in-depth video walkthrough of the functionality:

  * [Watch "Building Agentic Apps with Angular and Genkit live!"](https://youtube.com/live/mx7yZoIa2n4?feature=share)
  * [Watch "Building Agentic Apps with Angular and Genkit live! PT 2"](https://youtube.com/live/YR6LN5_o3B0?feature=share)

* [Building Agentic apps with Firebase and Google Cloud (Barista Example)](https://developers.google.com/solutions/learn/agentic-barista) - Learn how to build an agentic coffee ordering app with Firebase and Google Cloud. This example uses both Firebase AI Logic and Genkit.

* [Creating Dynamic, Server-Driven UIs](https://github.com/angular/examples/tree/main/dynamic-sdui-app) - Learn to build Agentic Angular apps with UI views that are generated at runtime based on user input.

  This example also has an in-depth video walkthrough of the functionality:

  * [Watch "Exploring the future of web apps"](https://www.youtube.com/live/4qargCqOu70?feature=share)

### [Build AI-powered applications with Firebase AI Logic and Angular](#build-ai-powered-applications-with-firebase-ai-logic-and-angular)

[Firebase AI Logic](https://firebase.google.com/products/vertex-ai-in-firebase) provides a secure way to interact with Vertex AI Gemini API or Imagen API directly from your web and mobile apps. This is compelling for Angular developers since apps can be either full-stack or client-side only. If you are developing a client-side only application, Firebase AI Logic is a good fit for incorporating AI into your web apps.

Here is an example of how to build with Firebase AI Logic and Angular:

* [Firebase AI Logic x Angular Starter Kit](https://github.com/angular/examples/tree/main/firebase-ai-logic-angular-example) - Use this starter-kit to build an e-commerce application with a chat agent that can perform tasks. Start here if you do not have experience building with Firebase AI Logic and Angular.

  This example includes an [in-depth video walkthrough explaining the functionality and demonstrates how to add new features](https://youtube.com/live/4vfDz2al_BI).

### [Build AI-powered applications with Gemini API and Angular](#build-ai-powered-applications-with-gemini-api-and-angular)

The [Gemini API](https://ai.google.dev/gemini-api/docs) provides access to state-of-the-art models from Google that support audio, images, video, and text input. These models that are optimized for specific use cases, [learn more on the Gemini API documentation site](https://ai.google.dev/gemini-api/docs/models).

* [AI Text Editor Angular app template](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-text-editor) - Use this template to start with a fully functioning text editor with AI-powered features like refining text, expanding text and formalizing text. This is a good starting point to gain experience with calling the Gemini API via HTTP.

* [AI Chatbot app template](https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/angular/ai-chatbot) - This template starts with a chatbot user interface that communicates with the Gemini API via HTTP.

## [Best Practices](#best-practices)

### [Connecting to model providers and keeping your API Credentials Secure](#connecting-to-model-providers-and-keeping-your-api-credentials-secure)

When connecting to model providers, it is important to keep your API secrets safe. *Never put your API key in a file that ships to the client, such as `environments.ts`*.

Your application's architecture determines which AI APIs and tools to choose. Specifically, choose based on whether or not your application is client-side or server-side. Tools such as Firebase AI Logic provide a secure connection to the model APIs for client-side code. If you want to use a different API than Firebase AI Logic or prefer to use a different model provider, consider creating a proxy-server or even [Cloud Functions for Firebase](https://firebase.google.com/docs/functions) to serve as a proxy and not expose your API keys.

For an example of connecting using a client-side app, see the code: [Firebase AI Logic Angular example repository](https://github.com/angular/examples/tree/main/firebase-ai-logic-angular-example).

For server-side connections to model APIs that require API keys, prefer using a secrets manager or environment variable, not `environments.ts`. You should follow standard best practices for securing API keys and credentials. Firebase now provides a new secrets manager with the latest updates from Firebase App Hosting. To learn more, [check out the official documentation](https://firebase.google.com/docs/app-hosting/configure).

For a server-side connection example in a full-stack application, see the code: [Angular AI Example (Genkit and Angular Story Generator) repository](https://github.com/angular/examples/tree/main/genkit-angular-story-generator).

### [Use Tool Calling to enhance apps](#use-tool-calling-to-enhance-apps)

If you want to build agentic workflows, where agents are able to act and use tools to solve problems based on prompts use "tool calling". Tool calling, also known as function calling, is a way to provide LLMs the ability to make requests back to the application that called it. As a developer, you define which tools are available and you are in control of how or when the tools are called.

Tool calling further enhances your web apps by expanding your AI integration further than a question and answer style chat bot. In fact, you can empower your model to request function calls using the function calling API of your model provider. The available tools can be used to perform more complex actions within the context of your application.

In the [e-commerce example](https://github.com/angular/examples/blob/main/firebase-ai-logic-angular-example/src/app/ai.service.ts#L88) of the [Angular examples repository](https://github.com/angular/examples), the LLM requests to make calls to functions for inventory in order to gain the necessary context to perform more complex tasks such as calculating how much a group of items in the store will cost. The scope of the available API is up to you as a developer just as is whether or not to call a function requested by the LLM. You remain in control of the flow of execution. You can expose specific functions of a service for example but not all functions of that service.

### [Handling non-deterministic responses](#handling-non-deterministic-responses)

Because models can return non-deterministic results, your applications should be designed with that in mind. Here are a few strategies that you can use in your application implementation:

* Adjust prompts and model parameters (such as [temperature](https://ai.google.dev/gemini-api/docs/prompting-strategies)) for more or less deterministic responses. You can [find out more in the prompting strategies section](https://ai.google.dev/gemini-api/docs/prompting-strategies) of [ai.google.dev](https://ai.google.dev/).
* Use the "human in the loop" strategy where a human verifies outputs before proceeding in a workflow. Build your application workflows to allow operators (humans or other models) to verify outputs and confirm key decisions.
* Employ tool (or function) calling and schema constraints to guide and restrict model responses to predefined formats, increasing response predictability.

Even considering these strategies and techniques, sensible fallbacks should be incorporated in your application design. Follow existing standards of application resiliency. For example, it is not acceptable for an application to crash if a resource or API is not available. In that scenario, an error message is displayed to the user and, if applicable, options for next steps are also displayed. Building AI-powered applications requires the same consideration. Confirm that the response is aligned with the expected output and provide a "safe landing" in case it is not aligned by way of [graceful degradation](https://developer.mozilla.org/en-US/docs/Glossary/Graceful_degradation). This also applies to API outages for LLM providers.

Consider this example: The LLM provider is not responding. A potential strategy to handle the outage is:

* Save the response from the user to use in a retry scenario (now or at a later time)
* Alert the user to the outage with an appropriate message that doesn't reveal sensitive information
* Resume the conversation at a later time once the services are available again.

## [Next steps](#next-steps)

To learn about LLM prompts and AI IDE setup, see the following guides:

----
url: https://angular.dev/api/cdk/testing/ComponentHarnessConstructor
----

```
    
      interface ComponentHarnessConstructor<T extends ComponentHarness> {}
    
    
```

[`ComponentHarness`](/api/cdk/testing/ComponentHarness) subclasses must specify a static `hostSelector` property that is used to find the host element for the corresponding component. This property should match the selector for the Angular component.

Jump to details

----
url: https://angular.dev/api/forms/signals/NativeInputParseError
----

```
    
      class NativeInputParseError extends BaseNgValidationError {}
    
    
```

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/aria/combobox/ComboboxPopup
----

```
    
      class ComboboxPopup implements OnInit ,OnDestroy {}
    
    
```

The combobox that the popup belongs to.

The element that serves as the control target for the popup.

The ID of the popup.

The ID of the active descendant in the popup.

The type of the popup (e.g., listbox, tree, grid, dialog).

@returns`void`

@returns`void`

## [Description](#description)

A structural directive that marks the `ng-template` to be used as the popup for a combobox. This content is conditionally rendered.

The content of the popup can be any element with the `ngComboboxWidget` directive.

```
<ng-template ngComboboxPopup>
  <div ngComboboxWidget>
    <!-- ... options ... -->
  </div>
</ng-template>
```

Jump to details

----
url: https://angular.dev/api/forms/ControlValueAccessor
----

```
    
      interface ControlValueAccessor {}
    
    
```

Writes a new value to the element.

This method is called by the forms API to write to the view when programmatic changes from model to view are requested.

@paramobj`any`

The new value for the element

@returns`void`

Usage notes

### [Write a value to the element](#write-a-value-to-the-element)

The following example writes a value to the native DOM element.

```
writeValue(value: any): void {
  this._renderer.setProperty(this._elementRef.nativeElement, 'value', value);
}
```

Registers a callback function that is called when the control's value changes in the UI.

This method is called by the forms API on initialization to update the form model when values propagate from the view to the model.

When implementing the `registerOnChange` method in your own value accessor, save the given function so your class calls it at the appropriate time.

@paramfn`any`

The callback function to register

@returns`void`

Usage notes

### [Store the change function](#store-the-change-function)

The following example stores the provided function as an internal method.

```
registerOnChange(fn: (_: any) => void): void {
  this._onChange = fn;
}
```

When the value changes in the UI, call the registered function to allow the forms API to update itself:

```
host: {
   '(change)': '_onChange($event.target.value)'
}
```

Registers a callback function that is called by the forms API on initialization to update the form model on blur.

When implementing `registerOnTouched` in your own value accessor, save the given function so your class calls it when the control should be considered blurred or "touched".

@paramfn`any`

The callback function to register

@returns`void`

Usage notes

### [Store the callback function](#store-the-callback-function)

The following example stores the provided function as an internal method.

```
registerOnTouched(fn: any): void {
  this._onTouched = fn;
}
```

On blur (or equivalent), your class should call the registered function to allow the forms API to update itself:

```
host: {
   '(blur)': '_onTouched()'
}
```

Function that is called by the forms API when the control status changes to or from 'DISABLED'. Depending on the status, it enables or disables the appropriate DOM element.

@paramisDisabled`boolean`

The disabled status to set on the element

@returns`void`

Usage notes

The following is an example of writing the disabled property to a native DOM element:

```
setDisabledState(isDisabled: boolean): void {
  this._renderer.setProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
}
```

## [Description](#description)

Defines an interface that acts as a bridge between the Angular forms API and a native element in the DOM.

Implement this interface to create a custom form control directive that integrates with Angular forms.

Jump to details

----
url: https://angular.dev/api/common/getLocaleNumberSymbol
----

Angular recommends relying on the `Intl` API for i18n. Use `Intl.NumberFormat` to format numbers instead.

## [API](/api/common/getLocaleNumberSymbol#api)

```
function getLocaleNumberSymbol(locale: string, symbol: NumberSymbol): string;
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Use `Intl.NumberFormat` to format numbers instead.

@paramlocale`string`

The locale code.

@paramsymbol`NumberSymbol`

The symbol to localize. Must be one of [`NumberSymbol`](/api/common/NumberSymbol).

@returns`string`

The character for the localized symbol.

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/takeUntilDestroyed
----

```
function takeUntilDestroyed<T>(
  destroyRef?: DestroyRef | undefined,
): MonoTypeOperatorFunction<T>;
```

@paramdestroyRef`DestroyRef | undefined`

optionally, the `DestroyRef` representing the current context. This can be passed explicitly to use [`takeUntilDestroyed`](/api/core/rxjs-interop/takeUntilDestroyed) outside of an [injection context](guide/di/dependency-injection-context). Otherwise, the current `DestroyRef` is injected.

@returns`MonoTypeOperatorFunction<T>`

Jump to details

----
url: https://angular.dev/api/forms/ValidatorFn
----

```
    
      interface ValidatorFn {  (control: AbstractControl<any, any, any>): ValidationErrors | null;}
    
    
```

@paramcontrol`AbstractControl<any, any, any>`

@returns`ValidationErrors | null`

Jump to details

----
url: https://angular.dev/api/forms/Validator
----

```
    
      interface Validator {}
    
    
```

Method that performs synchronous validation against the provided control.

@paramcontrol`AbstractControl<any, any, any>`

The control to validate against.

@returns`ValidationErrors | null`

A map of validation errors if validation fails, otherwise null.

Registers a callback function to call when the validator inputs change.

@paramfn`() => void`

The callback function

@returns`void`

## [Usage Notes](#usage-notes)

### [Provide a custom validator](#provide-a-custom-validator)

The following example implements the [`Validator`](/api/forms/Validator) interface to create a validator directive with a custom error key.

```
@Directive({
  selector: '[customValidator]',
  providers: [{provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidatorDirective), multi: true}]
})
class CustomValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors|null {
    return {'custom': true};
  }
}
```

Jump to details

----
url: https://angular.dev/api/forms/signals/pattern
----

```
function pattern<TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<string, 1, TPathKind>,
  pattern: RegExp | LogicFn<string | undefined, RegExp | undefined, TPathKind>,
  config?: BaseValidatorConfig<string, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<string, 1, TPathKind>`

Path of the field to validate

@parampattern`RegExp | LogicFn<string | undefined, RegExp | undefined, TPathKind>`

The RegExp pattern to match, or a LogicFn that returns the RegExp pattern.

@paramconfig`BaseValidatorConfig<string, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`patternError()`](/api/forms/signals/patternError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

Jump to details

----
url: https://angular.dev/guide/forms/signals/models
----

Form models are the foundation of Signal Forms, serving as the single source of truth for your form data. This guide explores how to create form models, update them, and design them for maintainability.

**NOTE:** Form models are distinct from Angular's [`model()`](/api/core/model) signal used for component two-way binding. A form model is a writable signal that stores form data, while [`model()`](/api/core/model) creates inputs/outputs for parent/child component communication.

## [What form models solve](/guide/forms/signals/models#what-form-models-solve)

Forms require managing data that changes over time. Without a clear structure, this data can become scattered across component properties, making it difficult to track changes, validate input, or submit data to a server.

Form models solve this by centralizing form data in a single writable signal. When the model updates, the form automatically reflects those changes. When users interact with the form, the model updates accordingly.

## [Creating models](/guide/forms/signals/models#creating-models)

A form model is a writable signal created with Angular's [`signal()`](/api/core/signal) function. The signal holds an object that represents your form's data structure.

```
import {Component, signal} from '@angular/core';
import {form, FormField} from '@angular/forms/signals';

@Component({
  selector: 'app-login',
  imports: [FormField],
  template: `
    <input type="email" [formField]="loginForm.email" />
    <input type="password" [formField]="loginForm.password" />
  `,
})
export class LoginComponent {
  loginModel = signal({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel);
}
```

The [`form()`](api/forms/signals/form) function accepts the model signal and creates a **field tree** - a special object structure that mirrors your model's shape. The field tree is both navigable (access child fields with dot notation like `loginForm.email`) and callable (call a field as a function to access its state).

The `[formField]` directive binds each input element to its corresponding field in the field tree, enabling automatic two-way synchronization between the UI and model.

### [Supported model structures](/guide/forms/signals/models#supported-model-structures)

Signal Forms builds the field tree by walking your model. The objects and arrays it walks through (the **structural layer**) must be plain JavaScript objects and arrays. The values at the **leaves** (positions with no nested fields) are usually primitives (strings, numbers, booleans) or `null`. Native `date`, `month`, `time`, and `week` inputs also accept `Date`, and custom controls can accept any value type they understand.

Prefer

### Plain structure

```
interface UserFormModel {
  name: string;
  birthday: Date | null;
  preferences: {
    theme: string;
    notifications: boolean;
  };
  tags: string[];
}

const userModel = signal<UserFormModel>({
  name: '',
  birthday: null,
  preferences: {
    theme: 'dark',
    notifications: true,
  },
  tags: [],
});
```

**IMPORTANT:** Class instances, `Map`, and `Set` are **not supported in the structural layer**, even though TypeScript will accept them. Signal Forms does not validate the model shape at runtime, so the framework accepts these values without throwing, then produces incorrect behavior in different ways depending on shape:

* **Class instances** lose their prototype on the first write because Signal Forms shallow-copies parent objects on update. Methods, getters, and `instanceof` checks are gone afterward.
* **Non-extensible or frozen objects inside arrays** throw when Signal Forms assigns a tracking symbol to preserve item identity across reorders.
* **`Map` and `Set`** produce empty field trees, because Signal Forms enumerates children with `Object.keys`.

If your application uses classes for domain modeling, translate to plain objects at the form boundary. See [Translating between form model and domain model](guide/forms/signals/model-design#translating-between-form-model-and-domain-model).

### [Using TypeScript types](/guide/forms/signals/models#using-typescript-types)

While TypeScript infers types from object literals, defining explicit types improves code quality and provides better IntelliSense support.

```
interface LoginData {
  email: string;
  password: string;
}

export class LoginComponent {
  loginModel = signal<LoginData>({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel);
}
```

With explicit types, the field tree provides full type safety. Accessing `loginForm.email` is typed as `FieldTree<string>`, and attempting to access a non-existent property results in a compile-time error.

```
// TypeScript knows this is FieldTree<string>
const emailField = loginForm.email;

// TypeScript error: Property 'username' does not exist
const usernameField = loginForm.username;
```

### [Initializing all fields](/guide/forms/signals/models#initializing-all-fields)

Form models should provide initial values for all fields you want to include in the field tree.

Prefer

```
// Good: All fields initialized
const userModel = signal({
  name: '',
  email: '',
  age: 0,
});
```

Avoid

```
// Avoid: Missing initial value
const userModel = signal({
  name: '',
  email: '',
  // age field is not defined - cannot access userForm.age
});
```

For optional fields, explicitly set them to an empty value or `null`:

```
interface UserData {
  name: string;
  email: string;
  phoneNumber: string | null;
}

const userModel = signal<UserData>({
  name: '',
  email: '',
  phoneNumber: null,
});
```

**HELPFUL:** Native text controls like `<input type=text>` and `<textarea>` don't support `null`, use `''` to represent an empty value.

Fields set to `undefined` are excluded from the field tree. A model with `{value: undefined}` behaves identically to `{}` - accessing the field returns `undefined` rather than a [`FieldTree`](/api/forms/signals/FieldTree).

## [Reading model values](/guide/forms/signals/models#reading-model-values)

You can access form values in two ways: directly from the model signal, or through individual fields. Each approach serves a different purpose.

### [Reading from the model](/guide/forms/signals/models#reading-from-the-model)

Access the model signal when you need the complete form data, such as during form submission:

```
async onSubmit() {
  const formData = this.loginModel();
  console.log(formData.email, formData.password);

  // Send to server
  await this.authService.login(formData);
}
```

The model signal returns the entire data object, making it ideal for operations that work with the complete form state.

### [Reading from field state](/guide/forms/signals/models#reading-from-field-state)

Each field in the field tree is a function. Calling a field returns a [`FieldState`](/api/forms/signals/FieldState) object containing reactive signals for the field's value, validation status, and interaction state.

Access field state when working with individual fields in templates or reactive computations:

```
@Component({
  template: `
    <p>Current email: {{ loginForm.email().value() }}</p>
    <p>Password length: {{ passwordLength() }}</p>
  `,
})
export class LoginComponent {
  loginModel = signal({email: '', password: ''});
  loginForm = form(this.loginModel);

  passwordLength = computed(() => {
    return this.loginForm.password().value().length;
  });
}
```

Field state provides reactive signals for each field's value, making it suitable for displaying field-specific information or creating derived state.

**TIP:** Field state includes many more signals beyond `value()`, such as validation state (e.g., valid, invalid, errors), interaction tracking (e.g., touched, dirty), and visibility (e.g., hidden, disabled).

See the [Field State Management guide](guide/forms/signals/field-state-management) for complete coverage.

## [Updating form models programmatically](/guide/forms/signals/models#updating-form-models-programmatically)

### [Replacing form models with `set()`](/guide/forms/signals/models#replacing-form-models-with-set)

Use `set()` on the form model to replace the entire value:

```
loadUserData() {
  this.userModel.set({
    name: 'Alice',
    email: 'alice@example.com',
    age: 30,
  });
}

resetForm() {
  this.userModel.set({
    name: '',
    email: '',
    age: 0,
  });
}
```

This approach works well when loading data from an API or resetting the entire form.

### [Update a single field directly with `set()` or `update()`](/guide/forms/signals/models#update-a-single-field-directly-with-set-or-update)

Use `set()` on individual field values to directly update the field state:

```
clearEmail() {
  this.userForm.email().value.set('');
}

incrementAge() {
  this.userForm.age().value.update(currentAge => currentAge + 1);
}
```

These are also known as "field-level updates." They automatically propagate to the model signal and keep both in sync.

### [Example: Loading data from an API](/guide/forms/signals/models#example-loading-data-from-an-api)

A common pattern involves fetching data and populating the model:

```
export class UserProfileComponent {
  userModel = signal({
    name: '',
    email: '',
    bio: '',
  });

  userForm = form(this.userModel);
  private userService = inject(UserService);

  ngOnInit() {
    this.loadUserProfile();
  }

  async loadUserProfile() {
    const userData = await this.userService.getUserProfile();
    this.userModel.set(userData);
  }
}
```

The form fields automatically update when the model changes, displaying the fetched data without additional code.

## [Two-way data binding](/guide/forms/signals/models#two-way-data-binding)

The `[formField]` directive creates automatic two-way synchronization between the model, form state, and UI.

### [How data flows](/guide/forms/signals/models#how-data-flows)

Changes flow bidirectionally:

**User input → Model:**

1. User types in an input element
2. The `[formField]` directive detects the change
3. Field state updates
4. Model signal updates

**Programmatic update → UI:**

1. Code updates the model with `set()` or `update()`
2. Model signal notifies subscribers
3. Field state updates
4. The `[formField]` directive updates the input element

This synchronization happens automatically. You don't write subscriptions or event handlers to keep the model and UI in sync.

### [Example: Both directions](/guide/forms/signals/models#example-both-directions)

```
@Component({
  template: `
    <input type="text" [formField]="userForm.name" />
    <button (click)="setName('Bob')">Set Name to Bob</button>
    <p>Current name: {{ userModel().name }}</p>
  `,
})
export class UserComponent {
  userModel = signal({name: ''});
  userForm = form(this.userModel);

  setName(name: string) {
    this.userForm.name().value.set(name);
    // Input automatically displays 'Bob'
  }
}
```

When the user types in the input, `userModel().name` updates. When the button is clicked, the input value changes to "Bob". No manual synchronization code is required.

## [Model structure patterns](/guide/forms/signals/models#model-structure-patterns)

Form models can be flat objects or contain nested objects and arrays. The structure you choose affects how you access fields and organize validation.

### [Flat vs nested models](/guide/forms/signals/models#flat-vs-nested-models)

Flat form models keep all fields at the top level:

```
// Flat structure
const userModel = signal({
  name: '',
  email: '',
  street: '',
  city: '',
  state: '',
  zip: '',
});
```

Nested models group related fields:

```
// Nested structure
const userModel = signal({
  name: '',
  email: '',
  address: {
    street: '',
    city: '',
    state: '',
    zip: '',
  },
});
```

**Use flat structures when:**

* Fields don't have clear conceptual groupings
* You want simpler field access (`userForm.city` vs `userForm.address.city`)
* Validation rules span multiple potential groups

**Use nested structures when:**

* Fields form a clear conceptual group (like an address)
* The grouped data matches your API structure
* You want to validate the group as a unit

### [Working with nested objects](/guide/forms/signals/models#working-with-nested-objects)

You can access nested fields by following the object path:

```
const userModel = signal({
  profile: {
    firstName: '',
    lastName: '',
  },
  settings: {
    theme: 'light',
    notifications: true,
  },
});

const userForm = form(userModel);

// Access nested fields
userForm.profile.firstName; // FieldTree<string>
userForm.settings.theme; // FieldTree<string>
```

In templates, you bind nested fields the same way as top-level fields:

```
@Component({
  template: `
    <input [formField]="userForm.profile.firstName" />
    <input [formField]="userForm.profile.lastName" />

    <select [formField]="userForm.settings.theme">
      <option value="light">Light</option>
      <option value="dark">Dark</option>
    </select>
  `,
})
```

### [Working with arrays](/guide/forms/signals/models#working-with-arrays)

Models can include arrays for collections of items:

```
const orderModel = signal({
  customerName: '',
  items: [{product: '', quantity: 0, price: 0}],
});

const orderForm = form(orderModel);

// Access array items by index
orderForm.items[0].product; // FieldTree<string>
orderForm.items[0].quantity; // FieldTree<number>
```

Array items containing objects automatically receive tracking identities, which helps maintain field state even when items change position in the array. This ensures validation state and user interactions persist correctly when arrays are reordered.

## [Next steps](/guide/forms/signals/models#next-steps)

This guide covered creating models and updating values. Related guides explore other aspects of Signal Forms:

----
url: https://angular.dev/tutorials/first-app/14-http
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/13-search)

[](/tutorials/first-app/13-search)

This tutorial demonstrates how to integrate HTTP and an API into your app.

Up until this point your app has read data from a static array in an Angular service. The next step is to use a JSON server that your app will communicate with over HTTP. The HTTP request will simulate the experience of working with data from a server.

[](https://www.youtube.com/watch?v=5K10oYJ5Y-E\&autoplay=1)

**IMPORTANT:** We recommend using your local environment for this step of the tutorial.

## [What you'll learn](#what-youll-learn)

Your app will use data from a JSON server

1. ### [Configure the JSON server](#configure-the-json-server)

   JSON Server is an open source tool used to create mock REST APIs. You'll use it to serve the housing location data that is currently stored in the housing service.

   1. Install `json-server` from npm by using the following command.

      ```
      npm install -g json-server
      ```

   2. In the root directory of your project, create a file called `db.json`. This is where you will store the data for the `json-server`.

   3. Open `db.json` and copy the following code into the file

      ```
      {
        "locations": [
          {
            "id": 0,
            "name": "Acme Fresh Start Housing",
            "city": "Chicago",
            "state": "IL",
            "photo": "https://angular.dev/assets/images/tutorials/common/bernard-hermant-CLKGGwIBTaY-unsplash.jpg",
            "availableUnits": 4,
            "wifi": true,
            "laundry": true
          },
          {
            "id": 1,
            "name": "A113 Transitional Housing",
            "city": "Santa Monica",
            "state": "CA",
            "photo": "https://angular.dev/assets/images/tutorials/common/brandon-griggs-wR11KBaB86U-unsplash.jpg",
            "availableUnits": 0,
            "wifi": false,
            "laundry": true
          },
          {
            "id": 2,
            "name": "Warm Beds Housing Support",
            "city": "Juneau",
            "state": "AK",
            "photo": "https://angular.dev/assets/images/tutorials/common/i-do-nothing-but-love-lAyXdl1-Wmc-unsplash.jpg",
            "availableUnits": 1,
            "wifi": false,
            "laundry": false
          },
          {
            "id": 3,
            "name": "Homesteady Housing",
            "city": "Chicago",
            "state": "IL",
            "photo": "https://angular.dev/assets/images/tutorials/common/ian-macdonald-W8z6aiwfi1E-unsplash.jpg",
            "availableUnits": 1,
            "wifi": true,
            "laundry": false
          },
          {
            "id": 4,
            "name": "Happy Homes Group",
            "city": "Gary",
            "state": "IN",
            "photo": "https://angular.dev/assets/images/tutorials/common/krzysztof-hepner-978RAXoXnH4-unsplash.jpg",
            "availableUnits": 1,
            "wifi": true,
            "laundry": false
          },
          {
            "id": 5,
            "name": "Hopeful Apartment Group",
            "city": "Oakland",
            "state": "CA",
            "photo": "https://angular.dev/assets/images/tutorials/common/r-architecture-JvQ0Q5IkeMM-unsplash.jpg",
            "availableUnits": 2,
            "wifi": true,
            "laundry": true
          },
          {
            "id": 6,
            "name": "Seriously Safe Towns",
            "city": "Oakland",
            "state": "CA",
            "photo": "https://angular.dev/assets/images/tutorials/common/phil-hearing-IYfp2Ixe9nM-unsplash.jpg",
            "availableUnits": 5,
            "wifi": true,
            "laundry": true
          },
          {
            "id": 7,
            "name": "Hopeful Housing Solutions",
            "city": "Oakland",
            "state": "CA",
            "photo": "https://angular.dev/assets/images/tutorials/common/r-architecture-GGupkreKwxA-unsplash.jpg",
            "availableUnits": 2,
            "wifi": true,
            "laundry": true
          },
          {
            "id": 8,
            "name": "Seriously Safe Towns",
            "city": "Oakland",
            "state": "CA",
            "photo": "https://angular.dev/assets/images/tutorials/common/saru-robert-9rP3mxf8qWI-unsplash.jpg",
            "availableUnits": 10,
            "wifi": false,
            "laundry": false
          },
          {
            "id": 9,
            "name": "Capital Safe Towns",
            "city": "Portland",
            "state": "OR",
            "photo": "https://angular.dev/assets/images/tutorials/common/webaliser-_TPTXZd9mOo-unsplash.jpg",
            "availableUnits": 6,
            "wifi": true,
            "laundry": true
          }
        ]
      }
      ```

   4. Save this file.

   5. Time to test your configuration. From the command line, at the root of your project run the following commands.

      ```
      json-server --watch db.json
      ```

   6. In your web browser, navigate to the `http://localhost:3000/locations` and confirm that the response includes the data stored in `db.json`.

   If you have any trouble with your configuration, you can find more details in the [official documentation](https://www.npmjs.com/package/json-server).

2. ### [Update service to use web server instead of local array](#update-service-to-use-web-server-instead-of-local-array)

   The data source has been configured, the next step is to update your web app to connect to it use the data.

   1. In `src/app/housing.service.ts`, make the following changes:

   2. Update the code to remove `housingLocationList` property and the array containing the data, as well as the `baseUrl` property.

   3. Add a string property called `url` and set its value to `'http://localhost:3000/locations'`

      ### Add url property to housing.service.ts

      ```
      import {Service} from '@angular/core';
      import {HousingLocationInfo} from './housinglocation';

      @Service()
      export class HousingService {
        url = 'http://localhost:3000/locations';

        async getAllHousingLocations(): Promise<HousingLocationInfo[]> {
          const data = await fetch(this.url);
          return (await data.json()) ?? [];
        }

        async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> {
          const data = await fetch(`${this.url}/${id}`);
          const locationJson = await data.json();
          return locationJson ?? {};
        }

        submitApplication(firstName: string, lastName: string, email: string) {
          // tslint:disable-next-line
          console.log(firstName, lastName, email);
        }
      }
      ```

      This code will result in errors in the rest of the file because it depends on the `housingLocationList` property. We're going to update the service methods next.

   4. Update the `getAllHousingLocations` function to make a call to the web server you configured.

      ### Update the getAllHousingLocations method in housing.service.ts

      ```
      import {Service} from '@angular/core';
      import {HousingLocationInfo} from './housinglocation';

      @Service()
      export class HousingService {
        url = 'http://localhost:3000/locations';

        async getAllHousingLocations(): Promise<HousingLocationInfo[]> {
          const data = await fetch(this.url);
          return (await data.json()) ?? [];
        }

        async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> {
          const data = await fetch(`${this.url}/${id}`);
          const locationJson = await data.json();
          return locationJson ?? {};
        }

        submitApplication(firstName: string, lastName: string, email: string) {
          // tslint:disable-next-line
          console.log(firstName, lastName, email);
        }
      }
      ```

      The code now uses asynchronous code to make a **GET** request over HTTP.

      **HELPFUL:** For this example, the code uses `fetch`. For more advanced use cases consider using [`HttpClient`](/api/common/http/HttpClient) provided by Angular.

   5. Update the `getHousingLocationById` function to make a call to the web server you configured.

      **HELPFUL:** Notice the `fetch` URL now appends the `id` as a path segment (`${this.url}/${id}`) to request the location with a matching `id` property value.

      ### Update the getHousingLocationById method in housing.service.ts

      ```
      import {Service} from '@angular/core';
      import {HousingLocationInfo} from './housinglocation';

      @Service()
      export class HousingService {
        url = 'http://localhost:3000/locations';

        async getAllHousingLocations(): Promise<HousingLocationInfo[]> {
          const data = await fetch(this.url);
          return (await data.json()) ?? [];
        }

        async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> {
          const data = await fetch(`${this.url}/${id}`);
          const locationJson = await data.json();
          return locationJson ?? {};
        }

        submitApplication(firstName: string, lastName: string, email: string) {
          // tslint:disable-next-line
          console.log(firstName, lastName, email);
        }
      }
      ```

   6. Once all the updates are complete, your updated service should match the following code.

      ### Final version of housing.service.ts

      ```
      import {Service} from '@angular/core';
      import {HousingLocationInfo} from './housinglocation';

      @Service()
      export class HousingService {
        url = 'http://localhost:3000/locations';

        async getAllHousingLocations(): Promise<HousingLocationInfo[]> {
          const data = await fetch(this.url);
          return (await data.json()) ?? [];
        }

        async getHousingLocationById(id: number): Promise<HousingLocationInfo | undefined> {
          const data = await fetch(`${this.url}/${id}`);
          const locationJson = await data.json();
          return locationJson ?? {};
        }

        submitApplication(firstName: string, lastName: string, email: string) {
          // tslint:disable-next-line
          console.log(firstName, lastName, email);
        }
      }
      ```

3. ### [Update the components to use asynchronous calls to the housing service](#update-the-components-to-use-asynchronous-calls-to-the-housing-service)

   The server is now reading data from the HTTP request but the components that rely on the service now have errors because they were programmed to use the synchronous version of the service.

   1. In `src/app/home/home.ts`, update the `constructor` to use the new asynchronous version of the `getAllHousingLocations` method. Because we didn't use signals for our state, you have to notify Angular that a change happened that requires a synchronization. Call `this.changeDetectorRef.markForCheck()` to do this.

      ### Update constructor in home.ts

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';
      import {HousingService} from '../housing.service';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" #filter />
              <button class="primary" type="button" (click)="filterResults(filter.value)">Search</button>
            </form>
          </section>
          <section class="results">
            @for (housingLocation of filteredLocationList; track $index) {
              <app-housing-location [housingLocation]="housingLocation" />
            }
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        housingLocationList: HousingLocationInfo[] = [];
        housingService: HousingService = inject(HousingService);
        filteredLocationList: HousingLocationInfo[] = [];

        constructor() {
          this.housingService
            .getAllHousingLocations()
            .then((housingLocationList: HousingLocationInfo[]) => {
              this.housingLocationList = housingLocationList;
              this.filteredLocationList = housingLocationList;
              this.changeDetectorRef.markForCheck();
            });
        }

        filterResults(text: string) {
          if (!text) {
            this.filteredLocationList = this.housingLocationList;
            return;
          }

          this.filteredLocationList = this.housingLocationList.filter((housingLocation) =>
            housingLocation?.city.toLowerCase().includes(text.toLowerCase()),
          );
        }
      }
      ```

   2. In `src/app/details/details.ts`, update the `constructor` to use the new asynchronous version of the `getHousingLocationById` method. As before, you must also call `this.changeDetectorRef.markForCheck()` to notify Angular of the changes.

      ### Update constructor in details.ts

      ```
      import {ChangeDetectorRef, Component, inject} from '@angular/core';
      import {ActivatedRoute} from '@angular/router';
      import {HousingService} from '../housing.service';
      import {HousingLocationInfo} from '../housinglocation';
      import {FormControl, FormGroup, ReactiveFormsModule} from '@angular/forms';

      @Component({
        selector: 'app-details',
        imports: [ReactiveFormsModule],
        template: `
          <article>
            <img
              class="listing-photo"
              [src]="housingLocation?.photo"
              alt="Exterior photo of {{ housingLocation?.name }}"
              crossorigin
            />
            <section class="listing-description">
              <h2 class="listing-heading">{{ housingLocation?.name }}</h2>
              <p class="listing-location">{{ housingLocation?.city }}, {{ housingLocation?.state }}</p>
            </section>
            <section class="listing-features">
              <h2 class="section-heading">About this housing location</h2>
              <ul>
                <li>Units available: {{ housingLocation?.availableUnits }}</li>
                <li>Does this location have wifi: {{ housingLocation?.wifi }}</li>
                <li>Does this location have laundry: {{ housingLocation?.laundry }}</li>
              </ul>
            </section>
            <section class="listing-apply">
              <h2 class="section-heading">Apply now to live here</h2>
              <form [formGroup]="applyForm" (submit)="submitApplication()">
                <label for="first-name">First Name</label>
                <input id="first-name" type="text" formControlName="firstName" />

                <label for="last-name">Last Name</label>
                <input id="last-name" type="text" formControlName="lastName" />

                <label for="email">Email</label>
                <input id="email" type="email" formControlName="email" />
                <button type="submit" class="primary">Apply now</button>
              </form>
            </section>
          </article>
        `,
        styleUrls: ['./details.css'],
      })
      export class Details {
        private readonly changeDetectorRef = inject(ChangeDetectorRef);
        route: ActivatedRoute = inject(ActivatedRoute);
        housingService = inject(HousingService);
        housingLocation: HousingLocationInfo | undefined;

        applyForm = new FormGroup({
          firstName: new FormControl(''),
          lastName: new FormControl(''),
          email: new FormControl(''),
        });

        constructor() {
          const housingLocationId = parseInt(this.route.snapshot.params['id'], 10);
          this.housingService.getHousingLocationById(housingLocationId).then((housingLocation) => {
            this.housingLocation = housingLocation;
            this.changeDetectorRef.markForCheck();
          });
        }

        submitApplication() {
          this.housingService.submitApplication(
            this.applyForm.value.firstName ?? '',
            this.applyForm.value.lastName ?? '',
            this.applyForm.value.email ?? '',
          );
        }
      }
      ```

   3. Save your code.

   4. Open the application in the browser and confirm that it runs without any errors.

**NOTE:** This lesson relies on the `fetch` browser API. For the support of interceptors, please refer to the [HTTP client documentation](/guide/http)

**SUMMARY:** In this lesson, you updated your app to use a local web server (`json-server`), and use asynchronous service methods to retrieve data.

Congratulations! You've successfully completed this tutorial and are ready to continue your journey with building even more complex Angular Apps.

If you would like to learn more, please consider completing some of Angular's other developer [tutorials](tutorials) and [guides](overview).

----
url: https://angular.dev/api/core/REQUEST
----

```
    
      const REQUEST: InjectionToken<Request | null>;
    
    
```

## [Description](#description)

Injection token representing the current HTTP request object.

Use this token to access the current request when handling server-side rendering (SSR).

## [Usage Notes](#usage-notes)

This token may be `null` in the following scenarios:

* During the build processes.
* When the application is rendered in the browser (client-side rendering).
* When performing static site generation (SSG).
* During route extraction in development (at the time of the request).

Jump to details

----
url: https://angular.dev/api/core/globals/getRootComponents
----

```
function getRootComponents(elementOrDir: {} | Element): {}[];
```

@paramelementOrDir`{} | Element`

DOM element, component or directive instance for which to retrieve the root components.

@returns`{}[]`

Root components associated with the target object.

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragExit
----

```
    
      interface CdkDragExit<T = any, I = T> {}
    
    
```

Container from which the user has a removed an item.

Item that was removed from the container.

Jump to details

----
url: https://angular.dev/cli/lint
----

```
ng lint [project] [options]
```

The command takes an optional project name, as specified in the `projects` section of the `angular.json` workspace configuration file. When a project name is not supplied, executes the `lint` builder for all projects.

To use the `ng lint` command, use `ng add` to add a package that implements linting capabilities. Adding the package automatically updates your workspace configuration, adding a lint [CLI builder](tools/cli/cli-builder). For example:

```
"projects": {
  "my-project": {
    ...
    "architect": {
      ...
      "lint": {
        "builder": "@angular-eslint/builder:lint",
        "options": {}
      }
    }
  }
}
```

----
url: https://angular.dev/ecosystem/service-workers
----

**IMPORTANT:** The Angular Service Worker is a basic caching utility for simple offline support with a limited featureset. We will not be accepting any new features other than security fixes. For more advanced caching and offline capabilities, we recommend exploring native browser APIs directly.

Service workers augment the traditional web deployment model and empower applications to deliver a user experience with the reliability and performance on par with code that is written to run on your operating system and hardware. Adding a service worker to an Angular application is one of the steps for turning an application into a [Progressive Web App](https://web.dev/progressive-web-apps/) (also known as a PWA).

At its simplest, a service worker is a script that runs in the web browser and manages caching for an application.

Service workers function as a network proxy. They intercept all outgoing HTTP requests made by the application and can choose how to respond to them. For example, they can query a local cache and deliver a cached response if one is available. Proxying isn't limited to requests made through programmatic APIs, such as `fetch`; it also includes resources referenced in HTML and even the initial request to `index.html`. Service worker-based caching is thus completely programmable and doesn't rely on server-specified caching headers.

Unlike the other scripts that make up an application, such as the Angular application bundle, the service worker is preserved after the user closes the tab. The next time that browser loads the application, the service worker loads first, and can intercept every request for resources to load the application. If the service worker is designed to do so, it can *completely satisfy the loading of the application, without the need for the network*.

Even across a fast reliable network, round-trip delays can introduce significant latency when loading the application. Using a service worker to reduce dependency on the network can significantly improve the user experience.

## [Service workers in Angular](#service-workers-in-angular)

Angular applications, as single-page applications, are in a prime position to benefit from the advantages of service workers. Angular ships with a service worker implementation. Angular developers can take advantage of this service worker and benefit from the increased reliability and performance it provides, without needing to code against low-level APIs.

Angular's service worker is designed to optimize the end user experience of using an application over a slow or unreliable network connection, while also minimizing the risks of serving outdated content.

To achieve this, the Angular service worker follows these guidelines:

* Caching an application is like installing a native application. The application is cached as one unit, and all files update together.

* A running application continues to run with the same version of all files. It does not suddenly start receiving cached files from a newer version, which are likely incompatible.

* When users refresh the application, they see the latest fully cached version. New tabs load the latest cached code.

* Updates happen in the background, relatively quickly after changes are published. The previous version of the application is served until an update is installed and ready.

* The service worker conserves bandwidth when possible. Resources are only downloaded if they've changed.

To support these behaviors, the Angular service worker loads a *manifest* file from the server. The file, called `ngsw.json` (not to be confused with the [web app manifest](https://developer.mozilla.org/docs/Web/Manifest)), describes the resources to cache and includes hashes of every file's contents. When an update to the application is deployed, the contents of the manifest change, informing the service worker that a new version of the application should be downloaded and cached. This manifest is generated from a CLI-generated configuration file called `ngsw-config.json`.

Installing the Angular service worker is as straightforward as [running an Angular CLI command](ecosystem/service-workers/getting-started#adding-a-service-worker-to-your-project). In addition to registering the Angular service worker with the browser, this also makes a few services available for injection which interact with the service worker and can be used to control it. For example, an application can ask to be notified when a new update becomes available, or an application can ask the service worker to check the server for available updates.

## [Before you start](#before-you-start)

To make use of all the features of Angular service workers, use the latest versions of Angular and the [Angular CLI](tools/cli).

For service workers to be registered, the application must be accessed over HTTPS, not HTTP. Browsers ignore service workers on pages that are served over an insecure connection. The reason is that service workers are quite powerful, so extra care is needed to ensure the service worker script has not been tampered with.

There is one exception to this rule: to make local development more straightforward, browsers do *not* require a secure connection when accessing an application on `localhost`.

### [Browser support](#browser-support)

To benefit from the Angular service worker, your application must run in a web browser that supports service workers in general. Currently, service workers are supported in the latest versions of Chrome, Firefox, Edge, Safari, Opera, UC Browser (Android version) and Samsung Internet. Browsers like IE and Opera Mini do not support service workers.

If the user is accessing your application with a browser that does not support service workers, the service worker is not registered and related behavior such as offline cache management and push notifications does not happen. More specifically:

* The browser does not download the service worker script and the `ngsw.json` manifest file
* Active attempts to interact with the service worker, such as calling [`SwUpdate.checkForUpdate()`](/api/service-worker/SwUpdate#checkForUpdate), return rejected promises
* The observable events of related services, such as [`SwUpdate.versionUpdates`](/api/service-worker/SwUpdate#versionUpdates), are not triggered

It is highly recommended that you ensure that your application works even without service worker support in the browser. Although an unsupported browser ignores service worker caching, it still reports errors if the application attempts to interact with the service worker. For example, calling [`SwUpdate.checkForUpdate()`](/api/service-worker/SwUpdate#checkForUpdate) returns rejected promises. To avoid such an error, check whether the Angular service worker is enabled using [`SwUpdate.isEnabled`](/api/service-worker/SwUpdate#isEnabled).

To learn more about other browsers that are service worker ready, see the [Can I Use](https://caniuse.com/#feat=serviceworkers) page and [MDN docs](https://developer.mozilla.org/docs/Web/API/Service_Worker_API).

## [Related resources](#related-resources)

The rest of the articles in this section specifically address the Angular implementation of service workers.

For more information about service workers in general, see [Service Workers: an Introduction](https://developers.google.com/web/fundamentals/primers/service-workers).

For more information about browser support, see the [browser support](https://developers.google.com/web/fundamentals/primers/service-workers/#browser_support) section of [Service Workers: an Introduction](https://developers.google.com/web/fundamentals/primers/service-workers), Jake Archibald's [Is Serviceworker ready?](https://jakearchibald.github.io/isserviceworkerready), and [Can I Use](https://caniuse.com/serviceworkers).

For additional recommendations and examples, see:

## [Next step](#next-step)

To begin using Angular service workers, see [Getting Started with service workers](ecosystem/service-workers/getting-started).

----
url: https://angular.dev/guide/forms
----

# Forms in Angular

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/forms/overview.md "Edit this page")

Handling user input with forms is the cornerstone of many common applications.

Applications use forms to enable users to log in, to update a profile, to enter sensitive information, and to perform many other data-entry tasks.

Angular provides two different approaches to handling user input through forms: reactive and template-driven.

Both capture user input events from the view, validate the input, create a form and data model, and provide a way to track changes.

**TIP:** If you're looking for the new Signal Forms, check out our [essential Signal Forms guide](/essentials/signal-forms)!

This guide provides information to help you decide which type of form works best for your situation. It introduces the common building blocks used by both approaches. It also summarizes the key differences between the two approaches, and demonstrates those differences in the context of setup, data flow, and testing.

## [Choosing an approach](#choosing-an-approach)

Reactive forms and template-driven forms process and manage form data differently. Each approach offers different advantages.

| Forms                 | Details                                                                                                                                                                                                                                                                                                                                                                                                             |
| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Reactive forms        | Provide direct, explicit access to the underlying form's object model. Compared to template-driven forms, they are more robust: they're more scalable, reusable, and testable. If forms are a key part of your application, or you're already using reactive patterns for building your application, use reactive forms.                                                                                            |
| Template-driven forms | Rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app, such as an email list signup form. They're straightforward to add to an app, but they don't scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, template-driven forms could be a good fit. |

### [Key differences](#key-differences)

The following table summarizes the key differences between reactive and template-driven forms.

|                                                   | Reactive                             | Template-driven                 |
| :------------------------------------------------ | :----------------------------------- | :------------------------------ |
| [Setup of form model](#setting-up-the-form-model) | Explicit, created in component class | Implicit, created by directives |
| [Data model](#mutability-of-the-data-model)       | Structured and immutable             | Unstructured and mutable        |
| [Data flow](#data-flow-in-forms)                  | Synchronous                          | Asynchronous                    |
| [Form validation](#form-validation)               | Functions                            | Directives                      |

### [Scalability](#scalability)

If forms are a central part of your application, scalability is very important. Being able to reuse form models across components is critical.

Reactive forms are more scalable than template-driven forms. They provide direct access to the underlying form API, and use [synchronous data flow](#data-flow-in-reactive-forms) between the view and the data model, which makes creating large-scale forms easier. Reactive forms require less setup for testing, and testing does not require deep understanding of change detection to properly test form updates and validation.

Template-driven forms focus on simple scenarios and are not as reusable. They abstract away the underlying form API, and use [asynchronous data flow](#data-flow-in-template-driven-forms) between the view and the data model. The abstraction of template-driven forms also affects testing. Tests are deeply reliant on manual change detection execution to run properly, and require more setup.

## [Setting up the form model](#setting-up-the-form-model)

Both reactive and template-driven forms track value changes between the form input elements that users interact with and the form data in your component model. The two approaches share underlying building blocks, but differ in how you create and manage the common form-control instances.

### [Common form foundation classes](#common-form-foundation-classes)

Both reactive and template-driven forms are built on the following base classes.

| Base classes                                              | Details                                                                                                       |
| :-------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
| [`FormControl`](/api/forms/FormControl)                   | Tracks the value and validation status of an individual form control.                                         |
| [`FormGroup`](/api/forms/FormGroup)                       | Tracks the same values and status for a collection of form controls.                                          |
| [`FormArray`](/api/forms/FormArray)                       | Tracks the same values and status for an array of form controls.                                              |
| [`ControlValueAccessor`](/api/forms/ControlValueAccessor) | Creates a bridge between Angular [`FormControl`](/api/forms/FormControl) instances and built-in DOM elements. |

### [Setup in reactive forms](#setup-in-reactive-forms)

With reactive forms, you define the form model directly in the component class. The `[formControl]` directive links the explicitly created [`FormControl`](/api/forms/FormControl) instance to a specific form element in the view, using an internal value accessor.

The following component implements an input field for a single control, using reactive forms. In this example, the form model is the [`FormControl`](/api/forms/FormControl) instance.

```
import {Component} from '@angular/core';
import {FormControl, ReactiveFormsModule} from '@angular/forms';

@Component({
  selector: 'app-reactive-favorite-color',
  template: ` Favorite Color: <input type="text" [formControl]="favoriteColorControl" /> `,
  imports: [ReactiveFormsModule],
})
export class FavoriteColorReactive {
  favoriteColorControl = new FormControl('');
}
```

**IMPORTANT:** In reactive forms, the form model is the source of truth; it provides the value and status of the form element at any given point in time, through the `[formControl]` directive on the `<input>` element.

### [Setup in template-driven forms](#setup-in-template-driven-forms)

In template-driven forms, the form model is implicit, rather than explicit. The directive [`NgModel`](/api/forms/NgModel) creates and manages a [`FormControl`](/api/forms/FormControl) instance for a given form element.

The following component implements the same input field for a single control, using template-driven forms.

```
import {Component, signal} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-template-favorite-color',
  template: ` Favorite Color: <input type="text" [(ngModel)]="favoriteColor" /> `,
  imports: [FormsModule],
})
export class FavoriteColorTemplate {
  favoriteColor = signal('');
}
```

**IMPORTANT:** In a template-driven form, the source of truth is the template. The [`NgModel`](/api/forms/NgModel) directive automatically manages the [`FormControl`](/api/forms/FormControl) instance for you.

## [Data flow in forms](#data-flow-in-forms)

When an application contains a form, Angular must keep the view in sync with the component model and the component model in sync with the view. As users change values and make selections through the view, the new values must be reflected in the data model. Similarly, when the program logic changes values in the data model, those values must be reflected in the view.

Reactive and template-driven forms differ in how they handle data flowing from the user or from programmatic changes. The following diagrams illustrate both kinds of data flow for each type of form, using the favorite-color input field defined above.

### [Data flow in reactive forms](#data-flow-in-reactive-forms)

In reactive forms, each form element in the view is directly linked to the form model (a [`FormControl`](/api/forms/FormControl) instance). Updates from the view to the model and from the model to the view are synchronous and do not depend on how the UI is rendered.

The view-to-model diagram shows how data flows when an input field's value is changed from the view through the following steps:

1. The user types a value into the input element, in this case the favorite color *Blue*.
2. The form input element emits an "input" event with the latest value.
3. The [`ControlValueAccessor`](/api/forms/ControlValueAccessor) listening for events on the form input element immediately relays the new value to the [`FormControl`](/api/forms/FormControl) instance.
4. The [`FormControl`](/api/forms/FormControl) instance emits the new value through the `valueChanges` observable.
5. Any subscribers to the `valueChanges` observable receive the new value.

The model-to-view diagram shows how a programmatic change to the model is propagated to the view through the following steps.

1. The user calls the `favoriteColorControl.setValue()` method, which updates the [`FormControl`](/api/forms/FormControl) value.
2. The [`FormControl`](/api/forms/FormControl) instance emits the new value through the `valueChanges` observable.
3. Any subscribers to the `valueChanges` observable receive the new value.
4. The control value accessor on the form input element updates the element with the new value.

### [Data flow in template-driven forms](#data-flow-in-template-driven-forms)

In template-driven forms, each form element is linked to a directive that manages the form model internally.

The view-to-model diagram shows how data flows when an input field's value is changed from the view through the following steps.

1. The user types *Blue* into the input element.
2. The input element emits an "input" event with the value *Blue*.
3. The control value accessor attached to the input triggers the `setValue()` method on the [`FormControl`](/api/forms/FormControl) instance.
4. The [`FormControl`](/api/forms/FormControl) instance emits the new value through the `valueChanges` observable.
5. Any subscribers to the `valueChanges` observable receive the new value.
6. The control value accessor also calls the [`NgModel.viewToModelUpdate()`](/api/forms/NgModel#viewToModelUpdate) method which emits an `ngModelChange` event.
7. Because the component template uses two-way data binding for the `favoriteColor` property, the `favoriteColor` property in the component is updated to the value emitted by the `ngModelChange` event (*Blue*).

The model-to-view diagram shows how data flows from model to view when the `favoriteColor` changes from *Blue* to *Red*, through the following steps

1. The `favoriteColor` value is updated in the component.
2. Change detection begins.
3. During change detection, the `ngOnChanges` lifecycle hook is called on the [`NgModel`](/api/forms/NgModel) directive instance because the value of one of its inputs has changed.
4. The `ngOnChanges()` method queues an async task to set the value for the internal [`FormControl`](/api/forms/FormControl) instance.
5. Change detection completes.
6. On the next tick, the task to set the [`FormControl`](/api/forms/FormControl) instance value is executed.
7. The [`FormControl`](/api/forms/FormControl) instance emits the latest value through the `valueChanges` observable.
8. Any subscribers to the `valueChanges` observable receive the new value.
9. The control value accessor updates the form input element in the view with the latest `favoriteColor` value.

**NOTE:** [`NgModel`](/api/forms/NgModel) triggers a second change detection to avoid `ExpressionChangedAfterItHasBeenChecked` errors, because the value change originates in an input binding.

### [Mutability of the data model](#mutability-of-the-data-model)

The change-tracking method plays a role in the efficiency of your application.

| Forms                 | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reactive forms        | Keep the data model pure by providing it as an immutable data structure. Each time a change is triggered on the data model, the [`FormControl`](/api/forms/FormControl) instance returns a new data model rather than updating the existing data model. This gives you the ability to track unique changes to the data model through the control's observable. Change detection is more efficient because it only needs to update on unique changes. Because data updates follow reactive patterns, you can integrate with observable operators to transform data. |
| Template-driven forms | Rely on mutability with two-way data binding to update the data model in the component as changes are made in the template. Because there are no unique changes to track on the data model when using two-way data binding, change detection is less efficient at determining when updates are required.                                                                                                                                                                                                                                                           |

The difference is demonstrated in the previous examples that use the favorite-color input element.

* With reactive forms, the **[`FormControl`](/api/forms/FormControl) instance** always returns a new value when the control's value is updated
* With template-driven forms, the **favorite color property** is always modified to its new value

## [Form validation](#form-validation)

Validation is an integral part of managing any set of forms. Whether you're checking for required fields or querying an external API for an existing username, Angular provides a set of built-in validators as well as the ability to create custom validators.

| Forms                 | Details                                                                                                      |
| :-------------------- | :----------------------------------------------------------------------------------------------------------- |
| Reactive forms        | Define custom validators as **functions** that receive a control to validate                                 |
| Template-driven forms | Tied to template **directives**, and must provide custom validator directives that wrap validation functions |

For more information, see [Form Validation](guide/forms/form-validation#validating-input-in-reactive-forms).

## [Testing](#testing)

Testing plays a large part in complex applications. A simpler testing strategy is useful when validating that your forms function correctly. Reactive forms and template-driven forms have different levels of reliance on rendering the UI to perform assertions based on form control and form field changes. The following examples demonstrate the process of testing forms with reactive and template-driven forms.

### [Testing reactive forms](#testing-reactive-forms)

Reactive forms provide a relatively straightforward testing strategy because they provide synchronous access to the form and data models, and they can be tested without rendering the UI. In these tests, status and data are queried and manipulated through the control without interacting with the change detection cycle.

The following tests use the favorite-color components from previous examples to verify the view-to-model and model-to-view data flows for a reactive form.

#### [Verifying view-to-model data flow](#verifying-view-to-model-data-flow)

The first example performs the following steps to verify the view-to-model data flow.

1. Query the view for the form input element, and create a custom "input" event for the test.
2. Set the new value for the input to *Red*, and dispatch the "input" event on the form input element.
3. Assert that the component's `favoriteColorControl` value matches the value from the input.

### Favorite color test - view to model

```
it('should update the value of the input field', () => {
  const input = fixture.nativeElement.querySelector('input');
  const event = createNewEvent('input');

  input.value = 'Red';
  input.dispatchEvent(event);

  expect(fixture.componentInstance.favoriteColorControl.value).toEqual('Red');
});
```

The next example performs the following steps to verify the model-to-view data flow.

1. Use the `favoriteColorControl`, a [`FormControl`](/api/forms/FormControl) instance, to set the new value.
2. Query the view for the form input element.
3. Assert that the new value set on the control matches the value in the input.

### Favorite color test - model to view

```
it('should update the value in the control', () => {
  component.favoriteColorControl.setValue('Blue');

  const input = fixture.nativeElement.querySelector('input');

  expect(input.value).toBe('Blue');
});
```

### [Testing template-driven forms](#testing-template-driven-forms)

Writing tests with template-driven forms requires a detailed knowledge of the change detection process and an understanding of how directives run on each cycle to ensure that elements are queried, tested, or changed at the correct time.

The following tests use the favorite color components mentioned earlier to verify the data flows from view to model and model to view for a template-driven form.

The following test verifies the data flow from view to model.

### Favorite color test - view to model

```
it('should update the favorite color in the component', async () => {
  const input = fixture.nativeElement.querySelector('input');
  const event = createNewEvent('input');

  input.value = 'Red';
  input.dispatchEvent(event);

  await fixture.whenStable();

  expect(component.favoriteColor()).toEqual('Red');
});
```

Here are the steps performed in the view to model test.

1. Query the view for the form input element, and create a custom "input" event for the test.
2. Set the new value for the input to *Red*, and dispatch the "input" event on the form input element.
3. Run change detection through the test fixture.
4. Assert that the component `favoriteColor` property value matches the value from the input.

The following test verifies the data flow from model to view.

### Favorite color test - model to view

```
it('should update the favorite color on the input field', async () => {
  component.favoriteColor.set('Blue');

  await fixture.whenStable();

  const input = fixture.nativeElement.querySelector('input');
  expect(input.value).toBe('Blue');
});
```

Here are the steps performed in the model to view test.

1. Use the component instance to set the value of the `favoriteColor` property.
2. Run change detection through the test fixture.
3. Use `await fixture.whenStable()` to wait for the next rendering.
4. Query the view for the form input element.
5. Assert that the input value matches the value of the `favoriteColor` property in the component instance.

## [Next steps](#next-steps)

To learn more about reactive forms, see the following guides:

To learn more about template-driven forms, see the following guides:

----
url: https://angular.dev/api/common/NumberFormatStyle
----

[`getLocaleNumberFormat`](/api/common/getLocaleNumberFormat) is deprecated

## [API](#api)

```
    
      enum NumberFormatStyle {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/IdleService
----

## [API](/api/core/IdleService#api)

```
    
      interface IdleService {}
    
    
```

Schedule `callback` to be executed when the current application or browser is considered idle.

@paramcallback`(deadline?: IdleDeadline | undefined) => void`

@paramoptions`IdleRequestOptions | undefined`

@returns`number`

an id which allows the scheduled callback to be cancelled before it executes.

Cancel a previously scheduled callback using the id associated with it.

@paramid`number`

@returns`void`

Jump to details

----
url: https://angular.dev/api/common/http/withJsonpSupport
----

```
function withJsonpSupport(): HttpFeature<HttpFeatureKind.JsonpSupport>;
```

@deprecated

JSONP is deprecated as it can cause XSS vulnerabilities. Use standard HTTP requests instead. Intent to remove in future versions of Angular.

@returns`HttpFeature<HttpFeatureKind.JsonpSupport>`

Jump to details

----
url: https://angular.dev/api/forms/signals/CompatSchemaPath
----

```
    
      type CompatSchemaPath<TControl extends AbstractControl, TPathKind extends PathKind = PathKind.Root> = SchemaPath<  TControl extends AbstractControl<unknown, infer TValue> ? TValue : never,  SchemaPathRules.Unsupported,  TPathKind> &  // & also we capture the control type, so that `stateOf(p)` can unwrap  // to a correctly typed `CompatFieldState`.  {    [ɵɵTYPE]: {control: TControl};  }
    
    
```

Jump to details

----
url: https://angular.dev/cli/analytics/info
----

```
ng analytics info
```

Prints analytics gathering and reporting configuration in the console.

Jump to details

----
url: https://angular.dev/api/common/getLocaleEraNames
----

```
function getLocaleEraNames(
  locale: string,
  width: TranslationWidth,
): readonly [string, string];
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Use `Intl.DateTimeFormat` for date formating instead.

@paramlocale`string`

A locale code for the locale format rules to use.

@paramwidth`TranslationWidth`

The required character width.

@returns`readonly [string, string]`

An array of localized era strings. For example, `[AD, BC]` for `en-US`.

Jump to details

----
url: https://angular.dev/api/core/CreateSignalOptions
----

```
    
      interface CreateSignalOptions<T> {}
    
    
```

A comparison function which defines equality for signal values.

A debug name for the signal. Used in Angular DevTools to identify the signal.

Jump to details

----
url: https://angular.dev/api/router/ParamMap
----

```
    
      interface ParamMap {}
    
    
```

Reports whether the map contains a given parameter.

@paramname`string`

The parameter name.

@returns`boolean`

True if the map contains the given parameter, false otherwise.

Retrieves a single value for a parameter.

@paramname`string`

The parameter name.

@returns`string | null`

Retrieves multiple values for a parameter.

@paramname`string`

The parameter name.

@returns`string[]`

Names of the parameters in the map.

Jump to details

----
url: https://angular.dev/api/core/IterableDiffer
----

```
    
      interface IterableDiffer<V> {}
    
    
```

Compute a difference between the previous state and the new `object` state.

@paramobject`NgIterable<V> | null | undefined`

containing the new value.

@returns`IterableChanges<V> | null`

an object describing the difference. The return value is only valid until the next `diff()` invocation.

Jump to details

----
url: https://angular.dev/api/common/DecimalPipe
----

```
{{ value_expression | number:digitsInfo:locale }}
```

## [API](#api)

```
    
      class DecimalPipe implements PipeTransform {
}
    
    
```

@param\_locale`string`

@returns`DecimalPipe`

@paramvalue`string | number`

The value to be formatted.

@paramdigitsInfo`string | undefined`

Sets digit and decimal representation. [See more](#digitsinfo).

@paramlocale`string | undefined`

Specifies what locale format rules to use. [See more](#locale).

Formats a value according to digit options and locale rules. Locale determines group sizing and separator, decimal point character, and other locale-specific configurations.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

### [digitsInfo](#digitsinfo)

The value's decimal representation is specified by the `digitsInfo` parameter, written in the following format:

```
{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
```

* `minIntegerDigits`: The minimum number of integer digits before the decimal point. Default is 1.

* `minFractionDigits`: The minimum number of digits after the decimal point. Default is 0.

* `maxFractionDigits`: The maximum number of digits after the decimal point. Default is 3.

If the formatted value is truncated it will be rounded using the "to-nearest" method:

```
{{3.6 | number: '1.0-0'}}
<!--will output '4'-->

{{-3.6 | number:'1.0-0'}}
<!--will output '-4'-->
```

### [locale](#locale)

`locale` will format a value according to locale rules. Locale determines group sizing and separator, decimal point character, and other locale-specific configurations.

When not supplied, uses the value of [`LOCALE_ID`](/api/core/LOCALE_ID), which is `en-US` by default.

See [Setting your app locale](guide/i18n/locale-id).

### [Example](#example)

The following code shows how the pipe transforms values according to various format specifications, where the caller's default locale is `en-US`.

```
@Component({
  selector: 'number-pipe',
  imports: [DecimalPipe],
  template: `<div>
    <p>
      No specified formatting:
      {{ pi | number }}
      <!--output: '3.142'-->
    </p>

    <p>
      With digitsInfo parameter specified:
      {{ pi | number: '4.1-5' }}
      <!--output: '0,003.14159'-->
    </p>

    <p>
      With digitsInfo and locale parameters specified:
      {{ pi | number: '4.1-5' : 'fr' }}
      <!--output: '0 003,14159'-->
    </p>
  </div>`,
})
export class NumberPipeComponent {
  pi: number = 3.14159265359;
}
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8115
----

This diagnostic detects when a track function is not invoked in `@for` blocks.

```
import {Component} from '@angular/core';

@Component({
  template: `@for (item of items; track trackByName) {}`,
})
class Items {
  protected trackByName(item) {
    return item.name;
  }
}
```

## [What's wrong with that?](#whats-wrong-with-that)

`@for` blocks need to uniquely identify items in the iterable to correctly perform DOM updates when items in the iterable are reordered, new items are added, or existing items are removed. When you don't invoke the function, the reconcialiation algorithm will use the function reference instead of returned value to compare items.

## [What should I do instead?](#what-should-i-do-instead)

Ensure to invoke the track function when you use it in a `@for` block to execute the function so the loop can uniquely identify items.

```
import {Component} from '@angular/core';

@Component({
  template: `@for (item of items; track trackByName(item)) {}`,
})
class Items {
  protected trackByName(item) {
    return item.name;
  }
}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `uninvokedTrackFunction` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "uninvokedTrackFunction": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/forms/signals/FormUiControl
----

```
    
      interface FormUiControl<TValue> {}
    
    
```

An input to receive the errors for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind errors from the bound field to this input.

An input to receive the disabled status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the disabled status from the bound field to this input.

An input to receive the reasons for the disablement of the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the disabled reason from the bound field to this input.

An input to receive the readonly status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the readonly status from the bound field to this input.

An input to receive the hidden status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the hidden status from the bound field to this input.

An input to receive the invalid status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the invalid status from the bound field to this input.

An input to receive the pending status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the pending status from the bound field to this input.

An input to receive the touched status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the touched status from the bound field to this input.

An input to receive the dirty status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the dirty status from the bound field to this input.

An input to receive the name for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the name from the bound field to this input.

An input to receive the required status for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the required status from the bound field to this input.

An input to receive the min value for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the min value from the bound field to this input.

An input to receive the min length for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the min length from the bound field to this input.

An input to receive the max value for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the max value from the bound field to this input.

An input to receive the max length for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the max length from the bound field to this input.

An input to receive the value patterns for the field. If implemented, the [`Field`](/api/forms/signals/Field) directive will automatically bind the value patterns from the bound field to this input.

An output to emit when the user finishes interacting with the control, marking the field as touched. Emit this in response to the native `blur` event (when focus leaves the control), not `focus`. The [`Field`](/api/forms/signals/Field) directive listens to this output to update the field's touched status, which blur-based rules such as `debounce('blur')` rely on.

Focuses the UI control.

If the focus method is not implemented, Signal Forms will attempt to focus the host element when asked to focus this control.

@paramoptions`FocusOptions | undefined`

@returns`void`

Resets the UI control to its pristine state.

@returns`void`

Jump to details

----
url: https://angular.dev/api/service-worker/SwRegistrationOptions
----

```
    
      abstract class SwRegistrationOptions {}
    
    
```

Whether the ServiceWorker will be registered and the related services (such as [`SwPush`](/api/service-worker/SwPush) and [`SwUpdate`](/api/service-worker/SwUpdate)) will attempt to communicate and interact with it.

Default: true

The value of the setting used to determine the circumstances in which the browser will consult the HTTP cache when it tries to update the service worker or any scripts that are imported via importScripts(). [ServiceWorkerRegistration.updateViaCache](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/updateViaCache)

The type of the ServiceWorker script to register. [ServiceWorkerRegistration#type](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#type)

* `classic`: Registers the script as a classic worker. ES module features such as `import` and `export` are NOT allowed in the script.
* `module`: Registers the script as an ES module. Allows use of `import`/`export` syntax and module features.

@default 'classic'

A URL that defines the ServiceWorker's registration scope; that is, what range of URLs it can control. It will be used when calling [ServiceWorkerContainer#register()](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register).

Defines the ServiceWorker registration strategy, which determines when it will be registered with the browser.

The default behavior of registering once the application stabilizes (i.e. as soon as there are no pending micro- and macro-tasks) is designed to register the ServiceWorker as soon as possible but without affecting the application's first time load.

Still, there might be cases where you want more control over when the ServiceWorker is registered (for example, there might be a long-running timeout or polling interval, preventing the app from stabilizing). The available option are:

* `registerWhenStable:<timeout>`: Register as soon as the application stabilizes (no pending micro-/macro-tasks) but no later than `<timeout>` milliseconds. If the app hasn't stabilized after `<timeout>` milliseconds (for example, due to a recurrent asynchronous task), the ServiceWorker will be registered anyway. `<timeout>` is required. Without it, the ServiceWorker is registered immediately instead of waiting for the application to stabilize.
* `registerImmediately`: Register immediately.
* `registerWithDelay:<timeout>`: Register with a delay of `<timeout>` milliseconds. For example, use `registerWithDelay:5000` to register the ServiceWorker after 5 seconds. If `<timeout>` is omitted, is defaults to `0`, which will register the ServiceWorker as soon as possible but still asynchronously, once all pending micro-tasks are completed.
* An Observable factory function: A function that returns an `Observable`. The function will be used at runtime to obtain and subscribe to the `Observable` and the ServiceWorker will be registered as soon as the first value is emitted.

Default: 'registerWhenStable:30000'

## [Description](#description)

Token that can be used to provide options for [`ServiceWorkerModule`](/api/service-worker/ServiceWorkerModule) outside of [`ServiceWorkerModule.register()`](/api/service-worker/ServiceWorkerModule#register).

You can use this token to define a provider that generates the registration options at runtime, for example via a function call:

```
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ServiceWorkerModule, SwRegistrationOptions} from '@angular/service-worker';
@NgModule({
imports: [BrowserModule, ServiceWorkerModule.register('ngsw-worker.js')],
  providers: [
    {
      provide: SwRegistrationOptions,
      useFactory: () => ({enabled: location.search.includes('sw=true')}),
    },
  ],
})
export class AppModule {}
```

Jump to details

----
url: https://angular.dev/api/upgrade/static/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/provideHttpClient
----

```
function provideHttpClient(
  ...features: HttpFeature<HttpFeatureKind>[]
): EnvironmentProviders;
```

@paramfeatures`HttpFeature<HttpFeatureKind>[]`

@returns`EnvironmentProviders`

## [Description](#description)

Configures Angular's [`HttpClient`](/api/common/http/HttpClient) service to be available for injection.

The [`HttpClient`](/api/common/http/HttpClient) service is provided in the root by default.

By default, [`HttpClient`](/api/common/http/HttpClient) will be configured for injection with its default options for XSRF protection of outgoing requests. Additional configuration options can be provided by passing feature functions to [`provideHttpClient`](/api/common/http/provideHttpClient). For example, HTTP interceptors can be added using the `withInterceptors(...)` feature.

By default, [`HttpClient`](/api/common/http/HttpClient) uses the [`fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make requests. This is strongly recommended for applications that use Server-Side Rendering for better performance and compatibility. To use the `XMLHttpRequest` API instead, add the [`withXhr`](/api/common/http/withXhr) feature:

```
provideHttpClient(withXhr());
```

Jump to details

----
url: https://angular.dev/api/service-worker/VersionReadyEvent
----

```
    
      interface VersionReadyEvent {}
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG0502
----

This error is similar to the [error NG0500](errors/NG0500), but it indicates that Angular hydration process expected a node to be present in the DOM structure at a particular location and none was found. Please refer to the [error NG0500](errors/NG0500) page for additional information.

## [Debugging the error](#debugging-the-error)

See the [error NG0500](errors/NG0500) for more information on how to debug this error.

----
url: https://angular.dev/tutorials/first-app/07-dynamic-template-values
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/06-property-binding)[](/tutorials/first-app/08-ngFor)

[](/tutorials/first-app/06-property-binding)[](/tutorials/first-app/08-ngFor)

This tutorial lesson demonstrates how to add interpolation to Angular templates in order to display dynamic data in a template.

[](https://www.youtube.com/watch?v=eM3zi_n7lNs\&autoplay=1)

## [What you'll learn](#what-youll-learn)

* Your app will display interpolated values in the `HousingLocation` template.
* Your app will render a housing location data to the browser.

## [Conceptual preview of interpolation](#conceptual-preview-of-interpolation)

In this step you will display values read from `input` properties in a template using interpolation.

Using the `{{ expression }}` in Angular templates, you can render values from properties, `inputs`, and valid JavaScript expressions.

For a more in depth explanation, please refer to the [Displaying values with interpolation](guide/templates/binding#render-dynamic-text-with-text-interpolation) guide.

1. ### [Update `HousingLocation` template to include interpolated values](#update-housinglocation-template-to-include-interpolated-values)

   This step adds new HTML structure and interpolated values in the `HousingLocation` template.

   In the code editor:

   1. Navigate to `src/app/housing-location/housing-location.ts`

   2. In the template property of the [`@Component`](/api/core/Component) decorator, replace the existing HTML markup with the following code:

      ### Update HousingLocation template in housing-location.ts

      ```
      import {Component, input} from '@angular/core';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-housing-location',
        template: `
          <section class="listing">
            <img
              class="listing-photo"
              [src]="housingLocation().photo"
              alt="Exterior photo of {{ housingLocation().name }}"
              crossorigin
            />
            <h2 class="listing-heading">{{ housingLocation().name }}</h2>
            <p class="listing-location">{{ housingLocation().city }}, {{ housingLocation().state }}</p>
          </section>
        `,
        styleUrls: ['./housing-location.css'],
      })
      export class HousingLocation {
        housingLocation = input.required<HousingLocationInfo>();
      }
      ```

   In this updated template code you have used property binding to bind the `housingLocation.photo` to the `src` attribute. The `alt` attribute uses interpolation to give more context to the alt text of the image.

   You use interpolation to include the values for `name`, `city` and `state` of the `housingLocation` property.

2. ### [Confirm the changes render in the browser](#confirm-the-changes-render-in-the-browser)

   1. Save all changes.
   2. Open the browser and confirm that the app renders the photo, city and state sample data.

**SUMMARY:** In this lesson, you added a new HTML structure and used Angular template syntax to render values in the `HousingLocation` template.

Now, you have two important skills:

* passing data to components
* Interpolating values into a template

With these skills, your app can now share data and display dynamic values in the browser. Great work so far.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/tutorials/signals/8-using-signals-with-directives
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/7-using-signals-with-services)[](/tutorials/signals/9-query-child-elements-with-signal-queries)

[](/tutorials/signals/7-using-signals-with-services)[](/tutorials/signals/9-query-child-elements-with-signal-queries)

Now that you've learned [using signals with services](/tutorials/signals/7-using-signals-with-services), let's explore how directives use signals. **The great news: signals work exactly the same in directives as they do in components!** The main difference is that since directives don't have templates, you'll primarily use signals in host bindings to reactively update the host element.

In this activity, you'll build a highlight directive that demonstrates how signals create reactive behavior in directives.

***

1. ### [Set up signals just like in a component](#set-up-signals-just-like-in-a-component)

   Import the signal functions and create your reactive state. This works exactly the same as in components:

   ```
   import {Directive, input, signal, computed} from '@angular/core';

   @Directive({
     selector: '[highlight]',
   })
   export class HighlightDirective {
     // Signal inputs - same as components!
     color = input<string>('yellow');
     intensity = input<number>(0.3);

     // Internal state - same as components!
     private isHovered = signal(false);

     // Computed signals - same as components!
     backgroundStyle = computed(() => {
       const baseColor = this.color();
       const alpha = this.isHovered() ? this.intensity() : this.intensity() * 0.5;

       const colorMap: Record<string, string> = {
         'yellow': `rgba(255, 255, 0, ${alpha})`,
         'blue': `rgba(0, 100, 255, ${alpha})`,
         'green': `rgba(0, 200, 0, ${alpha})`,
         'red': `rgba(255, 0, 0, ${alpha})`,
       };

       return colorMap[baseColor] || colorMap['yellow'];
     });
   }
   ```

   Notice how this is identical to component patterns - the only difference is we're in a [`@Directive`](/api/core/Directive) instead of [`@Component`](/api/core/Component).

2. ### [Use signals in host bindings](#use-signals-in-host-bindings)

   Since directives don't have templates, you'll use signals in **host bindings** to reactively update the host element. Add the `host` configuration and event handlers:

   ```
   @Directive({
     selector: '[highlight]',
     host: {
       '[style.backgroundColor]': 'backgroundStyle()',
       '(mouseenter)': 'onMouseEnter()',
       '(mouseleave)': 'onMouseLeave()',
     },
   })
   export class HighlightDirective {
     // ... signals from previous step ...

     onMouseEnter() {
       this.isHovered.set(true);
     }

     onMouseLeave() {
       this.isHovered.set(false);
     }
   }
   ```

   The host bindings automatically re-evaluate when the signals change - just like template bindings in components! When `isHovered` changes, the `backgroundStyle` computed signal recalculates, and the host binding updates the element's style.

3. ### [Use the directive in your template](#use-the-directive-in-your-template)

   Update the app template to demonstrate the reactive directive:

   ```
   template: `
   <div>
     <h1>Directive with Signals</h1>

     <div highlight color="yellow" [intensity]="0.2">Hover me - Yellow highlight</div>

     <div highlight color="blue" [intensity]="0.4">Hover me - Blue highlight</div>

     <div highlight color="green" [intensity]="0.6">Hover me - Green highlight</div>
   </div>
   `,
   ```

   The directive automatically applies reactive highlighting based on the signal inputs!

Perfect! You've now seen how signals work with directives. Some key takeaways from this lesson are:

* **Signals are universal** - All signal APIs ([`input()`](/api/core/input), [`signal()`](/api/core/signal), [`computed()`](/api/core/computed), [`effect()`](/api/core/effect)) work the same in both directives and components
* **Host bindings are the primary use case** - Since directives don't have templates, you use signals in host bindings to reactively modify the host element
* **Same reactive patterns** - Signal updates trigger automatic re-evaluation of computed signals and host bindings, just like in component templates

In the next lesson, you'll [learn how to query child elements with signal queries](/tutorials/signals/9-query-child-elements-with-signal-queries)!

----
url: https://angular.dev/api/aria/toolbar/TOOLBAR_WIDGET_GROUP
----

```
    
      const TOOLBAR_WIDGET_GROUP: InjectionToken<ToolbarWidgetGroup>;
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser-dynamic/testing/platformBrowserDynamicTesting
----

Use the `platformBrowserTesting` function instead from `@angular/platform-browser/testing`. In case you are not in a CLI app and rely on JIT compilation, you might also need to import `@angular/compiler`

## [API](#api)

```
    
      const platformBrowserDynamicTesting: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
    
    
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8111
----

This diagnostic detects uninvoked functions in event bindings.

```
import {Component, signal, Signal} from '@angular/core';

@Component({
  template: `<button (click)="onClick">Click me</button>`,
})
class MyComponent {
  onClick() {
    console.log('clicked');
  }
}
```

## [What's wrong with that?](#whats-wrong-with-that)

Functions in event bindings should be invoked when the event is triggered. If the function is not invoked, it will not execute when the event is triggered.

## [What should I do instead?](#what-should-i-do-instead)

Ensure to invoke the function when you use it in an event binding to execute the function when the event is triggered.

```
import {Component} from '@angular/core';

@Component({
  template: `<button (click)="onClick()">Click me</button>`,
})
class MyComponent {
  onClick() {
    console.log('clicked');
  }
}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `uninvokedFunctionInEventBinding` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "uninvokedFunctionInEventBinding": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/tutorials/learn-angular/10-deferrable-views
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/9-output)[](/tutorials/learn-angular/11-optimizing-images)

[](/tutorials/learn-angular/9-output)[](/tutorials/learn-angular/11-optimizing-images)

Sometimes in app development, you end up with a lot of components that you need to reference in your app, but some of those don't need to be loaded right away for various reasons.

Maybe they are below the visible fold or are heavy components that aren't interacted with until later. In that case, we can load some of those resources later with deferrable views.

**NOTE:** Learn more about [deferred loading with @defer in the in-depth guide](/guide/templates/defer).

In this activity, you'll learn how to use deferrable views to defer load a section of your component template.

***

1. ### [Add a `@defer` block around the comments component](#add-a-defer-block-around-the-comments-component)

   In your app, the blog post page has a comment component after the post details.

   Wrap the comment component with a `@defer` block to defer load it.

   ```
   @defer {
     <comments />
   }
   ```

   The code above is an example of how to use a basic `@defer` block. By default `@defer` will load the `comments` component when the browser is idle.

2. ### [Add a placeholder](#add-a-placeholder)

   Add a `@placeholder` block to the `@defer` block. The `@placeholder` block is where you put html that will show before the deferred loading starts. The content in `@placeholder` blocks is eagerly loaded.

   ```
   @defer {
     <comments />
   } @placeholder {
     <p>Future comments</p>
   }
   ```

3. ### [Add a loading block](#add-a-loading-block)

   Add a `@loading` block to the `@defer` block. The `@loading` block is where you put html that will show *while* the deferred content is actively being fetched, but hasn't finished yet. The content in `@loading` blocks is eagerly loaded.

   ```
   @defer {
     <comments />
   } @placeholder {
     <p>Future comments</p>
   } @loading {
     <p>Loading comments...</p>
   }
   ```

4. ### [Add a minimum duration](#add-a-minimum-duration)

   Both `@placeholder` and `@loading` sections have optional parameters to prevent flickering from occurring when loading happens quickly. `@placeholder` has `minimum` and `@loading` has `minimum` and `after`. Add a `minimum` duration to the `@loading` block so it will be rendered for at least 2 seconds.

   ```
   @defer {
     <comments />
   } @placeholder {
     <p>Future comments</p>
   } @loading (minimum 2s) {
     <p>Loading comments...</p>
   }
   ```

5. ### [Add a viewport trigger](#add-a-viewport-trigger)

   Deferrable views have a number of trigger options. Add a viewport trigger so the content will defer load once it enters the viewport.

   ```
   @defer (on viewport) {
     <comments />
   } @placeholder {
     <p>Future comments</p>
   } @loading (minimum 2s) {
     <p>Loading comments...</p>
   }
   ```

6. ### [Add content](#add-content)

   A viewport trigger is best used when you're deferring content that's far enough down the page that it needs to be scrolled to see. So let's add some content to our blog post. You can either write your own, or you can copy the content below and put it inside the `<article>` element.

   ```
   <article>
     <p>
       Angular is my favorite framework, and this is why. Angular has the coolest deferrable view
       feature that makes defer loading content the easiest and most ergonomic it could possibly be.
       The Angular community is also filled with amazing contributors and experts that create excellent
       content. The community is welcoming and friendly, and it really is the best community out there.
     </p>
     <p>
       I can't express enough how much I enjoy working with Angular. It offers the best developer
       experience I've ever had. I love that the Angular team puts their developers first and takes
       care to make us very happy. They genuinely want Angular to be the best framework it can be, and
       they're doing such an amazing job at it, too. This statement comes from my heart and is not at
       all copied and pasted. In fact, I think I'll say these exact same things again a few times.
     </p>
     <p>
       Angular is my favorite framework, and this is why. Angular has the coolest deferrable view
       feature that makes defer loading content the easiest and most ergonomic it could possibly be.
       The Angular community is also filled with amazing contributors and experts that create excellent
       content. The community is welcoming and friendly, and it really is the best community out there.
     </p>
     <p>
       I can't express enough how much I enjoy working with Angular. It offers the best developer
       experience I've ever had. I love that the Angular team puts their developers first and takes
       care to make us very happy. They genuinely want Angular to be the best framework it can be, and
       they're doing such an amazing job at it, too. This statement comes from my heart and is not at
       all copied and pasted. In fact, I think I'll say these exact same things again a few times.
     </p>
     <p>
       Angular is my favorite framework, and this is why. Angular has the coolest deferrable view
       feature that makes defer loading content the easiest and most ergonomic it could possibly be.
       The Angular community is also filled with amazing contributors and experts that create excellent
       content. The community is welcoming and friendly, and it really is the best community out there.
     </p>
     <p>
       I can't express enough how much I enjoy working with Angular. It offers the best developer
       experience I've ever had. I love that the Angular team puts their developers first and takes
       care to make us very happy. They genuinely want Angular to be the best framework it can be, and
       they're doing such an amazing job at it, too. This statement comes from my heart and is not at
       all copied and pasted.
     </p>
   </article>
   ```

   Once you've added this code, now scroll down to see the deferred content load once you scroll it into the viewport.

In the activity, you've learned how to use deferrable views in your applications. Great work. 🙌

There's even more you can do with them, like different triggers, prefetching, and `@error` blocks.

If you would like to learn more, check out the [documentation for Deferrable views](/guide/templates/defer).

----
url: https://angular.dev/api/common/NgSwitch
----

Use the `@switch` block instead. Intent to remove in a future major release

## [API](#api)

```
    
      class NgSwitch {}
    
    
```

@deprecated

Use the `@switch` block instead. Intent to remove in a future major release

## [Description](#description)

The `[ngSwitch]` directive on a container specifies an expression to match against. The expressions to match are provided by `ngSwitchCase` directives on views within the container.

* Every view that matches is rendered.
* If there are no matches, a view with the `ngSwitchDefault` directive is rendered.
* Elements within the `[NgSwitch]` statement but outside of any [`NgSwitchCase`](/api/common/NgSwitchCase) or `ngSwitchDefault` directive are preserved at the location.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

Define a container element for the directive, and specify the switch expression to match against as an attribute:

```
<container-element [ngSwitch]="switch_expression">
```

Within the container, `*ngSwitchCase` statements specify the match expressions as attributes. Include `*ngSwitchDefault` as the final case.

```
<container-element [ngSwitch]="switch_expression">
   <some-element *ngSwitchCase="match_expression_1">...</some-element>
...
   <some-element *ngSwitchDefault>...</some-element>
</container-element>
```

### [Usage Examples](#usage-examples)

The following example shows how to use more than one case to display the same view:

```
<container-element [ngSwitch]="switch_expression">
  <!-- the same view can be shown in more than one case -->
  <some-element *ngSwitchCase="match_expression_1">...</some-element>
  <some-element *ngSwitchCase="match_expression_2">...</some-element>
  <some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
  <!--default case when there are no matches -->
  <some-element *ngSwitchDefault>...</some-element>
</container-element>
```

The following example shows how cases can be nested:

```
<container-element [ngSwitch]="switch_expression">
      <some-element *ngSwitchCase="match_expression_1">...</some-element>
      <some-element *ngSwitchCase="match_expression_2">...</some-element>
      <some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
      <ng-container *ngSwitchCase="match_expression_3">
        <!-- use a ng-container to group multiple root nodes -->
        <inner-element></inner-element>
        <inner-other-element></inner-other-element>
      </ng-container>
      <some-element *ngSwitchDefault>...</some-element>
    </container-element>
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/selenium-webdriver/SeleniumWebDriverHarnessEnvironment
----

```
    
      class SeleniumWebDriverHarnessEnvironment extends HarnessEnvironment<  () => webdriver.WebElement> {}
    
    
```

@paramrawRootElement`() => webdriver.WebElement`

@paramoptions`WebDriverHarnessEnvironmentOptions | undefined`

@returns`SeleniumWebDriverHarnessEnvironment`

Flushes change detection and async tasks captured in the Angular zone. In most cases it should not be necessary to call this manually. However, there may be some edge cases where it is needed to fully flush animation events.

@returns`Promise<void>`

Gets the root element for the document.

@returns`() => webdriver.WebElement`

Creates a `TestElement` from a raw element.

@paramelement`() => webdriver.WebElement`

@returns`TestElement`

Creates a `HarnessLoader` rooted at the given raw element.

@paramelement`() => webdriver.WebElement`

@returns`HarnessEnvironment<() => webdriver.WebElement>`

Gets a list of all elements matching the given selector under this environment's root element.

@paramselector`string`

@returns`Promise<(() => webdriver.WebElement)[]>`

Gets the ElementFinder corresponding to the given TestElement.

@paramel`TestElement`

@returns`webdriver.WebElement`

Creates a `HarnessLoader` rooted at the document root.

@paramdriver`webdriver.WebDriver`

@paramoptions`WebDriverHarnessEnvironmentOptions | undefined`

@returns`HarnessLoader`

Jump to details

----
url: https://angular.dev/api/router/DisabledInitialNavigationFeature
----

```
    
      type DisabledInitialNavigationFeature = RouterFeature<RouterFeatureKind.DisabledInitialNavigationFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/core/TRANSLATIONS_FORMAT
----

```
    
      const TRANSLATIONS_FORMAT: InjectionToken<string>;
    
    
```

## [Description](#description)

Provide this token at bootstrap to set the format of your [`TRANSLATIONS`](/api/core/TRANSLATIONS): `xtb`, `xlf` or `xlf2`.

See the [i18n guide](guide/i18n/merge) for more information.

## [Usage Notes](#usage-notes)

### [Example](#example)

In standalone apps:

```
import { LOCALE_ID, ApplicationConfig } from '@angular/core';

const appConfig: ApplicationConfig = {
  providers: [{provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }]
};
```

In module based apps: \*

```
import { TRANSLATIONS_FORMAT } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app.module';

platformBrowser().bootstrapModule(AppModule, {
  providers: [{provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }]
});
```

Jump to details

----
url: https://angular.dev/api/forms/signals/patternError
----

```
function patternError(
  pattern: RegExp,
  options: WithFieldTree<ValidationErrorOptions>,
): PatternValidationError;
function patternError(
  pattern: RegExp,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<PatternValidationError>;
```

Create a pattern matching error associated with the target field

@parampattern`RegExp`

The violated pattern

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`PatternValidationError`

Create a pattern matching error

@parampattern`RegExp`

The violated pattern

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<PatternValidationError>`

Jump to details

----
url: https://angular.dev/api/animations/trigger
----

```
function trigger(
  name: string,
  definitions: AnimationMetadata[],
): AnimationTriggerMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramname`string`

An identifying string.

@paramdefinitions`AnimationMetadata[]`

An animation definition object, containing an array of [`state()`](api/animations/state) and [`transition()`](/api/animations/transition) declarations.

@returns`AnimationTriggerMetadata`

## [Usage Notes](#usage-notes)

Define an animation trigger in the `animations` section of `@Component` metadata. In the template, reference the trigger by name and bind it to a trigger expression that evaluates to a defined animation state, using the following format:

`[@triggerName]="expression"`

Animation trigger bindings convert all values to strings, and then match the previous and current values against any linked transitions. Booleans can be specified as `1` or `true` and `0` or `false`.

### [Usage Example](#usage-example)

The following example creates an animation trigger reference based on the provided name value. The provided animation value is expected to be an array consisting of state and transition declarations.

```
@Component({
  selector: "my-component",
  templateUrl: "my-component-tpl.html",
  animations: [
    trigger("myAnimationTrigger", [
      state(...),
      state(...),
      transition(...),
      transition(...)
    ])
  ]
})
class MyComponent {
  myStatusExp = "something";
}
```

The template associated with this component makes use of the defined trigger by binding to an element within its template code.

```
<!-- somewhere inside of my-component-tpl.html -->
<div [@myAnimationTrigger]="myStatusExp">...</div>
```

### [Using an inline function](#using-an-inline-function)

The `transition` animation method also supports reading an inline function which can decide if its associated animation should be run.

```
// this method is run each time the `myAnimationTrigger` trigger value changes.
function myInlineMatcherFn(fromState: string, toState: string, element: any, params: {[key:
string]: any}): boolean {
  // notice that `element` and `params` are also available here
  return toState == 'yes-please-animate';
}

@Component({
  selector: 'my-component',
  templateUrl: 'my-component-tpl.html',
  animations: [
    trigger('myAnimationTrigger', [
      transition(myInlineMatcherFn, [
        // the animation sequence code
      ]),
    ])
  ]
})
class MyComponent {
  myStatusExp = "yes-please-animate";
}
```

### [Disabling Animations](#disabling-animations)

When true, the special animation control binding `@.disabled` binding prevents all animations from rendering. Place the `@.disabled` binding on an element to disable animations on the element itself, as well as any inner animation triggers within the element.

The following example shows how to use this feature:

```
@Component({
  selector: 'my-component',
  template: `
    <div [@.disabled]="isDisabled">
      <div [@childAnimation]="exp"></div>
    </div>
  `,
  animations: [
    trigger("childAnimation", [
      // ...
    ])
  ]
})
class MyComponent {
  isDisabled = true;
  exp = '...';
}
```

When `@.disabled` is true, it prevents the `@childAnimation` trigger from animating, along with any inner animations.

### [Disable animations application-wide](#disable-animations-application-wide)

When an area of the template is set to have animations disabled, **all** inner components have their animations disabled as well. This means that you can disable all animations for an app by placing a host binding set on `@.disabled` on the topmost Angular component.

```
import {Component, HostBinding} from '@angular/core';

@Component({
  selector: 'app-component',
  templateUrl: 'app.component.html',
})
class AppComponent {
  @HostBinding('@.disabled')
  public animationsDisabled = true;
}
```

### [Overriding disablement of inner animations](#overriding-disablement-of-inner-animations)

Despite inner animations being disabled, a parent animation can [`query()`](/api/animations/query) for inner elements located in disabled areas of the template and still animate them if needed. This is also the case for when a sub animation is queried by a parent and then later animated using [`animateChild()`](/api/animations/animateChild).

### [Detecting when an animation is disabled](#detecting-when-an-animation-is-disabled)

If a region of the DOM (or the entire application) has its animations disabled, the animation trigger callbacks still fire, but for zero seconds. When the callback fires, it provides an instance of an [`AnimationEvent`](/api/animations/AnimationEvent). If animations are disabled, the `.disabled` flag on the event is true.

Jump to details

----
url: https://angular.dev/guide/signals
----

In-depth Guides

Signals

# Angular Signals

[edit](https://github.com/angular/angular/edit/main/adev/src/content/guide/signals/overview.md "Edit this page")

Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates.

**TIP:** Check out Angular's [Essentials](essentials/signals) before diving into this comprehensive guide.

## [What are signals?](/guide/signals#what-are-signals)

A **signal** is a wrapper around a value that notifies interested consumers when that value changes. Signals can contain any value, from primitives to complex data structures.

You read a signal's value by calling its getter function, which allows Angular to track where the signal is used.

Signals may be either *writable* or *read-only*.

### [Writable signals](/guide/signals#writable-signals)

Writable signals provide an API for updating their values directly. You create writable signals by calling the [`signal`](/api/core/signal) function with the signal's initial value:

```
const count = signal(0);

// Signals are getter functions - calling them reads their value.
console.log('The count is: ' + count());
```

To change the value of a writable signal, either `.set()` it directly:

```
count.set(3);
```

or use the `.update()` operation to compute a new value from the previous one:

```
// Increment the count by 1.
count.update((value) => value + 1);
```

Writable signals have the type [`WritableSignal`](/api/core/WritableSignal).

#### [Converting writable signals to readonly](/guide/signals#converting-writable-signals-to-readonly)

[`WritableSignal`](/api/core/WritableSignal) provides an `asReadonly()` method that returns a readonly version of the signal. This is useful when you want to expose a signal's value to consumers without allowing them to modify it directly:

```
@Service()
export class CounterState {
  // Private writable state
  private readonly _count = signal(0);

  readonly count = this._count.asReadonly(); // public readonly

  increment() {
    this._count.update((v) => v + 1);
  }
}

@Component({/* ... */})
export class AwesomeCounter {
  state = inject(CounterState);

  count = this.state.count; // can read but not modify

  increment() {
    this.state.increment();
  }
}
```

The readonly signal reflects any changes made to the original writable signal, but cannot be modified using `set()` or `update()` methods.

**IMPORTANT:** The readonly signals do **not** have any built-in mechanism that would prevent deep-mutation of their value.

### [Computed signals](/guide/signals#computed-signals)

**Computed signals** are read-only signals that derive their value from other signals. You define computed signals using the [`computed`](/api/core/computed) function and specifying a derivation:

```
const count: WritableSignal<number> = signal(0);
const doubleCount: Signal<number> = computed(() => count() * 2);
```

The `doubleCount` signal depends on the `count` signal. Whenever `count` updates, Angular knows that `doubleCount` needs to update as well.

#### [Computed signals are both lazily evaluated and memoized](/guide/signals#computed-signals-are-both-lazily-evaluated-and-memoized)

`doubleCount`'s derivation function does not run to calculate its value until the first time you read `doubleCount`. The calculated value is then cached, and if you read `doubleCount` again, it will return the cached value without recalculating.

If you then change `count`, Angular knows that `doubleCount`'s cached value is no longer valid, and the next time you read `doubleCount` its new value will be calculated.

As a result, you can safely perform computationally expensive derivations in computed signals, such as filtering arrays.

#### [Computed signals are not writable signals](/guide/signals#computed-signals-are-not-writable-signals)

You cannot directly assign values to a computed signal. That is,

```
doubleCount.set(3);
```

produces a compilation error, because `doubleCount` is not a [`WritableSignal`](/api/core/WritableSignal).

#### [Computed signal dependencies are dynamic](/guide/signals#computed-signal-dependencies-are-dynamic)

Only the signals actually read during the derivation are tracked. For example, in this [`computed`](/api/core/computed) the `count` signal is only read if the `showCount` signal is true:

```
const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
  if (showCount()) {
    return `The count is ${count()}.`;
  } else {
    return 'Nothing to see here!';
  }
});
```

When you read `conditionalCount`, if `showCount` is `false` the "Nothing to see here!" message is returned *without* reading the `count` signal. This means that if you later update `count` it will *not* result in a recomputation of `conditionalCount`.

If you set `showCount` to `true` and then read `conditionalCount` again, the derivation will re-execute and take the branch where `showCount` is `true`, returning the message which shows the value of `count`. Changing `count` will then invalidate `conditionalCount`'s cached value.

Note that dependencies can be removed during a derivation as well as added. If you later set `showCount` back to `false`, then `count` will no longer be considered a dependency of `conditionalCount`.

## [Reactive contexts](/guide/signals#reactive-contexts)

A **reactive context** is a runtime state where Angular monitors signal reads to establish a dependency. The code reading the signal is the *consumer*, and the signal being read is the *producer*.

Angular automatically enters a reactive context when:

* Executing an [`effect`](/api/core/effect), [`afterRenderEffect`](/api/core/afterRenderEffect) callback.
* Evaluating a [`computed`](/api/core/computed) signal.
* Evaluating a [`linkedSignal`](/api/core/linkedSignal).
* Evaluating a [`resource`](/api/core/resource)'s params or loader function.
* Rendering a component template (including bindings in the [host property](guide/components/host-elements#binding-to-the-host-element)).

During these operations, Angular creates a *live* connection. If a tracked signal changes, Angular will *eventually* re-run the consumer.

### [Asserts the reactive context](/guide/signals#asserts-the-reactive-context)

Angular provides the [`assertNotInReactiveContext`](/api/core/assertNotInReactiveContext) helper function to assert that code is not executing within a reactive context. Pass a reference to the calling function so the error message points to the correct API entry point if the assertion fails. This produces a clearer, more actionable error message than a generic reactive context error.

```
import {assertNotInReactiveContext} from '@angular/core';

function subscribeToEvents() {
  assertNotInReactiveContext(subscribeToEvents);
  // Safe to proceed - subscription logic here
}
```

### [Reading without tracking dependencies](/guide/signals#reading-without-tracking-dependencies)

Rarely, you may want to execute code which may read signals within a reactive function such as [`computed`](/api/core/computed) or [`effect`](/api/core/effect) *without* creating a dependency.

For example, suppose that when `currentUser` changes, the value of a `counter` should be logged. You could create an [`effect`](/api/core/effect) which reads both signals:

```
effect(() => {
  console.log(`User set to ${currentUser()} and the counter is ${counter()}`);
});
```

This example will log a message when *either* `currentUser` or `counter` changes. However, if the effect should only run when `currentUser` changes, then the read of `counter` is only incidental and changes to `counter` shouldn't log a new message.

You can prevent a signal read from being tracked by calling its getter with [`untracked`](/api/core/untracked):

```
effect(() => {
  console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);
});
```

[`untracked`](/api/core/untracked) is also useful when an effect needs to invoke some external code which shouldn't be treated as a dependency:

```
effect(() => {
  const user = currentUser();
  untracked(() => {
    // If the `loggingService` reads signals, they won't be counted as
    // dependencies of this effect.
    this.loggingService.log(`User set to ${user}`);
  });
});
```

### [Reactive context and async operations](/guide/signals#reactive-context-and-async-operations)

The reactive context is only active for synchronous code. Any signal reads that occur after an asynchronous boundary will not be tracked as dependencies.

Avoid

```
effect(async () => {
  const data = await fetchUserData();
  // Reactive context is lost here - theme() won't be tracked
  console.log(`User: ${data.name}, Theme: ${theme()}`);
});
```

To ensure all signal reads are tracked, read signals before the `await`. This includes passing them as arguments to the awaited function, since arguments are evaluated synchronously:

Prefer

```
effect(async () => {
  const currentTheme = theme(); // Read before await
  const data = await fetchUserData();
  console.log(`User: ${data.name}, Theme: ${currentTheme}`);
});
```

Prefer

```
effect(async () => {
  // Also works: signal is read before await (as function argument)
  await renderContent(docContent());
});
```

## [Advanced derivations](/guide/signals#advanced-derivations)

While [`computed`](/api/core/computed) handles simple readonly derivations, you might find yourself needing a writable state that is dependent on other signals. For more information see the [Dependent state with linkedSignal](/guide/signals/linked-signal) guide.

All signal APIs are synchronous— [`signal`](/api/core/signal), [`computed`](/api/core/computed), `input`, etc. However, applications often need to deal with data that is available asynchronously. A [`Resource`](/api/core/Resource) gives you a way to incorporate async data into your application's signal-based code and still allow you to access its data synchronously. For more information see the [Async reactivity with resources](/guide/signals/resource) guide.

## [Executing side effects on non-reactive APIs](/guide/signals#executing-side-effects-on-non-reactive-apis)

Synchronous or asynchronous derivations are recommended when we want to react to state changes. However, this doesn't cover all the possible use cases, and you'll sometimes find yourself in a situation where you need to react to signal changes on non-reactive APIs. Use [`effect`](/api/core/effect) or [`afterRenderEffect`](/api/core/afterRenderEffect) for those specific use cases. For more information see [Side effects for non-reactive APIs](/guide/signals/effect) guide.

## [Reading signals in `OnPush` components](/guide/signals#reading-signals-in-onpush-components)

When you read a signal within an `OnPush` component's template, Angular tracks the signal as a dependency of that component. When the value of that signal changes, Angular automatically [marks](api/core/ChangeDetectorRef#markForCheck) the component to ensure it gets updated the next time change detection runs. Refer to the [Skipping component subtrees](best-practices/skipping-subtrees) guide for more information about `OnPush` components.

## [Advanced topics](/guide/signals#advanced-topics)

### [Signal equality functions](/guide/signals#signal-equality-functions)

When creating a signal, you can optionally provide an equality function, which will be used to check whether the new value is actually different than the previous one.

```
import isEqual from 'lodash/isEqual';

const data = signal(['test'], {equal: isEqual});

// Even though this is a different array instance, the deep equality
// function will consider the values to be equal, and the signal won't
// trigger any updates.
data.set(['test']);
```

Equality functions can be provided to both writable and computed signals.

**HELPFUL:** By default, signals use referential equality ([`Object.is()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison).

### [Type checking signals](/guide/signals#type-checking-signals)

You can use [`isSignal`](/api/core/isSignal) to check if a value is a [`Signal`](/api/core/Signal):

```
const count = signal(0);
const doubled = computed(() => count() * 2);

isSignal(count); // true
isSignal(doubled); // true
isSignal(42); // false
```

To specifically check if a signal is writable, use [`isWritableSignal`](/api/core/isWritableSignal):

```
const count = signal(0);
const doubled = computed(() => count() * 2);

isWritableSignal(count); // true
isWritableSignal(doubled); // false
```

## [Using signals with RxJS](/guide/signals#using-signals-with-rxjs)

See [RxJS interop with Angular signals](ecosystem/rxjs-interop) for details on interoperability between signals and RxJS.

## [Debugging signals](/guide/signals#debugging-signals)

Angular DevTools can visualize the signal graph of a component or directive, showing how its signals, computeds, effects, and other reactive nodes depend on each other. See [Debug the signal graph](tools/devtools/signals) to learn how to open, read, and inspect the graph.

----
url: https://angular.dev/reference/configs/npm-packages
----

The Angular Framework, Angular CLI, and components used by Angular applications are packaged as [npm packages](https://docs.npmjs.com/getting-started/what-is-npm "What is npm?") and distributed using the [npm registry](https://docs.npmjs.com).

You can download and install these npm packages by using the [npm CLI client](https://docs.npmjs.com/cli/install). By default, Angular CLI uses the npm client.

**HELPFUL:** See [Local Environment Setup](tools/cli/setup-local "Setting up for Local Development") for information about the required versions and installation of `Node.js` and `npm`.

If you already have projects running on your machine that use other versions of Node.js and npm, consider using [nvm](https://github.com/creationix/nvm) to manage the multiple versions of Node.js and npm.

## [`package.json`](/reference/configs/npm-packages#packagejson)

`npm` installs the packages identified in a [`package.json`](https://docs.npmjs.com/files/package.json) file.

The CLI command `ng new` creates a `package.json` file when it creates the new workspace. This `package.json` is used by all projects in the workspace, including the initial application project that is created by the CLI when it creates the workspace. Libraries created with `ng generate library` will include their own `package.json` file.

Initially, this `package.json` includes *a starter set of packages*, some of which are required by Angular and others that support common application scenarios. You add packages to `package.json` as your application evolves.

## [Default Dependencies](/reference/configs/npm-packages#default-dependencies)

The following Angular packages are included as dependencies in the default `package.json` file for a new Angular workspace. For a complete list of Angular packages, see the [API reference](api).

| Package name                                                     | Details                                                                                                                                                                                                          |
| :--------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`@angular/common`](api#angular_common)                          | The commonly-needed services, pipes, and directives provided by the Angular team.                                                                                                                                |
| `@angular/compiler`                                              | Angular's template compiler. It understands Angular templates and can convert them to code that makes the application run.                                                                                       |
| `@angular/compiler-cli`                                          | Angular's compiler which is invoked by the Angular CLI's `ng build` and `ng serve` commands. It processes Angular templates with `@angular/compiler` inside a standard TypeScript compilation.                   |
| [`@angular/core`](api#angular_core)                              | Critical runtime parts of the framework that are needed by every application. Includes all metadata decorators such as [`@Component`](/api/core/Component), dependency injection, and component lifecycle hooks. |
| [`@angular/forms`](api#angular_forms)                            | Support for both [template-driven](guide/forms) and [reactive forms](guide/forms/reactive-forms). See [Introduction to forms](guide/forms).                                                                      |
| [`@angular/platform-browser`](api#angular_platform-browser)      | Everything DOM and browser related, especially the pieces that help render into the DOM.                                                                                                                         |
| [`@angular/router`](api#angular_router)                          | The router module navigates among your application pages when the browser URL changes. For more information, see [Routing and Navigation](guide/routing).                                                        |
| [`@angular/cli`](https://github.com/angular/angular-cli)         | Contains the Angular CLI binary for running `ng` commands.                                                                                                                                                       |
| [`@angular/build`](https://www.npmjs.com/package/@angular/build) | Contains default CLI builders for bundling, testing, and serving Angular applications and libraries.                                                                                                             |
| [`rxjs`](https://www.npmjs.com/package/rxjs)                     | A library for reactive programming using `Observables`.                                                                                                                                                          |
| [`typescript`](https://www.npmjs.com/package/typescript)         | The TypeScript compiler, language server, and built-in type definitions.                                                                                                                                         |

----
url: https://angular.dev/api/common/PopStateEvent
----

```
    
      interface PopStateEvent {}
    
    
```

Whether the user agent performed a visual transition for this navigation.

Jump to details

----
url: https://angular.dev/api/core/CompilerOptions
----

```
    
      type CompilerOptions = {  defaultEncapsulation?: ViewEncapsulation;  providers?: StaticProvider[];  preserveWhitespaces?: boolean;}
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/ReadonlyCompatFieldState
----

```
    
      type ReadonlyCompatFieldState<TControl extends AbstractControl, TKey extends string | number = string | number> = CompatFieldState<TControl, TKey, 'readonly'>
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/By
----

```
    
      class By {}
    
    
```

Match all nodes.

@returns`Predicate<DebugNode>`

Usage notes

### [Example](#example)

```
debugElement.query(By.all());
```

Match elements by the given CSS selector.

@paramselector`string`

@returns`Predicate<DebugElement>`

Usage notes

### [Example](#example)

```
debugElement.query(By.css('[attribute]'));
```

Match nodes that have the given directive present.

@paramtype`Type<any>`

@returns`Predicate<DebugNode>`

Usage notes

### [Example](#example)

```
debugElement.query(By.directive(MyDirective));
```

Jump to details

----
url: https://angular.dev/api/router/testing/RouterTestingHarness
----

## [API](#api)

```
    
      class RouterTestingHarness {
}
    
    
```

@paramfixture`ComponentFixture<{ routerOutletData: WritableSignal<unknown>; }>`

@returns`RouterTestingHarness`

Fixture of the root component of the RouterTestingHarness

Instructs the root fixture to run change detection.

@returns`void`

The [`DebugElement`](/api/core/DebugElement) of the `RouterOutlet` component. `null` if the outlet is not activated.

The native element of the `RouterOutlet` component. `null` if the outlet is not activated.

Triggers a `Router` navigation and waits for it to complete.

The root component with a `RouterOutlet` created for the harness is used to render `Route` components. The root component is reused within the same test in subsequent calls to `navigateForTest`.

When testing `Routes` with a guards that reject the navigation, the `RouterOutlet` might not be activated and the `activatedComponent` may be `null`.

```
let isLoggedIn = false;
    const isLoggedInGuard: CanActivateFn = () => {
      return isLoggedIn ? true : inject(Router).parseUrl('/login');
    };

    TestBed.configureTestingModule({
      providers: [
        provideRouter([
          {path: 'admin', canActivate: [isLoggedInGuard], component: AdminComponent},
          {path: 'login', component: LoginComponent},
        ]),
      ],
    });

    const harness = await RouterTestingHarness.create('/admin');
    expect(TestBed.inject(Router).url).toEqual('/login');
    isLoggedIn = true;
    await harness.navigateByUrl('/admin');
    expect(TestBed.inject(Router).url).toEqual('/admin');
```

@paramurl`string`

The target of the navigation. Passed to `Router.navigateByUrl`.

@returns`Promise<{} | null>`

The activated component instance of the `RouterOutlet` after navigation completes (`null` if the outlet does not get activated).

Triggers a router navigation and waits for it to complete.

The root component with a `RouterOutlet` created for the harness is used to render `Route` components.

```
it('navigates to routed component', async () => {
    @Component({template: 'hello {{name}}'})
    class TestCmp {
      name = 'world';
    }

    TestBed.configureTestingModule({
      providers: [provideRouter([{path: '', component: TestCmp}])],
    });

    const harness = await RouterTestingHarness.create();
    const activatedComponent = await harness.navigateByUrl('/', TestCmp);
    expect(activatedComponent).toBeInstanceOf(TestCmp);
    expect(harness.routeNativeElement?.innerHTML).toContain('hello world');
  });
```

The root component is reused within the same test in subsequent calls to `navigateByUrl`.

This function also makes it easier to test components that depend on `ActivatedRoute` data.

```
@Component({
      imports: [AsyncPipe],
      template: `search: {{ (route.queryParams | async)?.query }}`,
    })
    class SearchCmp {
      constructor(
        readonly route: ActivatedRoute,
        readonly router: Router,
      ) {}

      async searchFor(thing: string) {
        await this.router.navigate([], {queryParams: {query: thing}});
      }
    }

    TestBed.configureTestingModule({
      providers: [provideRouter([{path: 'search', component: SearchCmp}])],
    });

    const harness = await RouterTestingHarness.create();
    const activatedComponent = await harness.navigateByUrl('/search', SearchCmp);
    await activatedComponent.searchFor('books');
    harness.detectChanges();
    expect(TestBed.inject(Router).url).toEqual('/search?query=books');
    expect(harness.routeNativeElement?.innerHTML).toContain('books');
```

@paramurl`string`

The target of the navigation. Passed to `Router.navigateByUrl`.

@paramrequiredRoutedComponentType`Type<T>`

After navigation completes, the required type for the activated component of the `RouterOutlet`. If the outlet is not activated or a different component is activated, this function will throw an error.

@returns`Promise<T>`

The activated component instance of the `RouterOutlet` after navigation completes.

Creates a [`RouterTestingHarness`](/api/router/testing/RouterTestingHarness) instance.

The [`RouterTestingHarness`](/api/router/testing/RouterTestingHarness) also creates its own root component with a `RouterOutlet` for the purposes of rendering route components.

Throws an error if an instance has already been created. Use of this harness also requires `destroyAfterEach: true` in the `ModuleTeardownOptions`

@paraminitialUrl`string | undefined`

The target of navigation to trigger before returning the harness.

@returns`Promise<RouterTestingHarness>`

Jump to details

----
url: https://angular.dev/api/core/PrefetchTrigger
----

```
    
      type PrefetchTrigger = () => Promise<void>
    
    
```

Jump to details

----
url: https://angular.dev/api/router/ResourceContext
----

```
    
      interface ResourceContext {}
    
    
```

The path and matrix parameters available to the route.

The query parameters of the route.

The URL fragment.

Data provided in the route configuration.

Jump to details

----
url: https://angular.dev/api/common/http/HttpProgressEvent
----

```
    
      interface HttpProgressEvent {}
    
    
```

Progress event type is either upload or download.

Number of bytes uploaded or downloaded.

Total number of bytes to upload or download. Depending on the request or response, this may not be computable and thus may not be present.

Jump to details

----
url: https://angular.dev/api/core/AfterRenderEffectOptions
----

```
    
      interface AfterRenderEffectOptions extends AfterRenderOptions {}
    
    
```

A debug name for the effect. Used in Angular DevTools to identify the effect.

The [`Injector`](/api/core/Injector) to use during creation.

If this is not provided, the current injection context will be used instead (via [`inject`](/api/core/inject)).

Whether the hook should require manual cleanup.

If this is `false` (the default) the hook will automatically register itself to be cleaned up with the current [`DestroyRef`](/api/core/DestroyRef).

Jump to details

----
url: https://angular.dev/api/forms/AbstractControlOptions
----

```
    
      interface AbstractControlOptions {}
    
    
```

The list of validators applied to a control.

The list of async validators applied to control.

The event name for control to update upon.

Jump to details

----
url: https://angular.dev/api/router/InitialNavigation
----

```
    
      type InitialNavigation = 'disabled' | 'enabledBlocking' | 'enabledNonBlocking'
    
    
```

## [Description](#description)

Allowed values in an [`ExtraOptions`](/api/router/ExtraOptions) object that configure when the router performs the initial navigation operation.

* 'enabledNonBlocking' - (default) The initial navigation starts after the root component has been created. The bootstrap is not blocked on the completion of the initial navigation.
* 'enabledBlocking' - The initial navigation starts before the root component is created. The bootstrap is blocked until the initial navigation is complete. This value should be set in case you use [server-side rendering](guide/ssr), but do not enable [hydration](guide/hydration) for your application.
* 'disabled' - The initial navigation is not performed. The location listener is set up before the root component gets created. Use if there is a reason to have more control over when the router starts its initial navigation due to some complex initialization logic.

Jump to details

----
url: https://angular.dev/api/forms/signals/hidden
----

```
function hidden<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  config?:
    | { when?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined }
    | undefined,
): void;
function hidden<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  logic?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined,
): void;
```

Adds logic to a field to conditionally hide it. A hidden field does not contribute to the validation, touched/dirty, or other state of its parent field.

If a field may be hidden it is recommended to guard it with an `@if` in the template:

```
@if (!email().hidden()) {
  <label for="email">Email</label>
  <input id="email" type="email" [control]="email" />
}
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to add the hidden logic to.

@paramconfig`{ when?: NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined; } | undefined`

Options object containing the `when` condition.

* `when`: A reactive function that returns `true` when the field is hidden.

@returns`void`

Adds logic to a field to conditionally hide it.

@deprecated

Passing a function directly to `hidden` is deprecated. Use `{ when: ... }` instead.

@parampath`SchemaPath<TValue, 1, TPathKind>`

@paramlogic`NoInfer<LogicFn<TValue, boolean, TPathKind>> | undefined`

@returns`void`

## [Description](#description)

Adds logic to a field to conditionally hide it. A hidden field does not contribute to the validation, touched/dirty, or other state of its parent field.

If a field may be hidden it is recommended to guard it with an `@if` in the template:

```
@if (!email().hidden()) {
  <label for="email">Email</label>
  <input id="email" type="email" [control]="email" />
}
```

Jump to details

----
url: https://angular.dev/ai/develop-with-ai
----

Generating code with large language models (LLMs) is a rapidly growing area of interest for developers. While LLMs are often capable of generating working code it can be a challenge to generate code for consistently evolving frameworks like Angular.

Advanced instructions and prompting are an emerging standard for supporting modern code generation with domain specific details. This section contains curated content and resources to support more accurate code generation for Angular and LLMs.

## [Custom Prompts and System Instructions](#custom-prompts-and-system-instructions)

Improve your experience generating code with LLMs by using one of the following custom, domain specific files.

**NOTE:** These files will be updated on a regular basis staying up to date with Angular's conventions.

Here is a set of instructions to help LLMs generate correct code that follows Angular best practices. This file can be included as system instructions to your AI tooling or included along with your prompt as context.

```
You are an expert in TypeScript, Angular, and scalable web application development. You write functional, maintainable, performant, and accessible code following Angular and TypeScript best practices.

## TypeScript Best Practices

- Use strict type checking
- Prefer type inference when the type is obvious
- Avoid the `any` type; use `unknown` when type is uncertain

## Angular Best Practices

- Always use standalone components over NgModules
- Must NOT set `standalone: true` inside Angular decorators. It's the default in Angular v20+.
- Do NOT set `changeDetection: ChangeDetectionStrategy.OnPush` explicitly. `OnPush` is the default in Angular v22+.
- Use signals for state management
- Implement lazy loading for feature routes
- Do NOT use the `@HostBinding` and `@HostListener` decorators. Put host bindings inside the `host` object of the `@Component` or `@Directive` decorator instead
- Use `NgOptimizedImage` for all static images.
  - `NgOptimizedImage` does not work for inline base64 images.

## Accessibility Requirements

- It MUST pass all AXE checks.
- It MUST follow all WCAG AA minimums, including focus management, color contrast, and ARIA attributes.

## Components

- Keep components small and focused on a single responsibility
- Use `input()` and `output()` functions instead of decorators
- Use `model()` for two-way bound properties with `[(prop)]` syntax instead of pairing `input()` with `output()`
- Use `computed()` for derived state
- Use `linkedSignal()` for state derived from multiple reactive sources that must stay synchronized
- Prefer inline templates for small components
- Prefer Signal Forms (`@angular/forms/signals`) for new forms. They are stable in Angular v22+ and provide signal-based state, type-safe field access, and schema-based validation
- When not using Signal Forms, prefer Reactive forms instead of Template-driven ones
- Do NOT use `ngClass`, use `class` bindings instead
- Do NOT use `ngStyle`, use `style` bindings instead
- Do NOT import `CommonModule`, import only the directives and pipes the template uses, such as `AsyncPipe` or `DatePipe`
- When using external templates/styles, use paths relative to the component TS file.

## State Management

- Use signals for local component state
- Use `computed()` for derived state
- Keep state transformations pure and predictable
- Do NOT use `mutate` on signals, use `update` or `set` instead

## Templates

- Keep templates simple and avoid complex logic
- Use native control flow (`@if`, `@for`, `@switch`) instead of `*ngIf`, `*ngFor`, `*ngSwitch`
- Use the async pipe to handle observables
- Do not assume globals like (`new Date()`) are available.

## Services

- Design services around a single responsibility
- Use the `providedIn: 'root'` option for singleton services
- Prefer the `@Service` decorator over `@Injectable({providedIn: 'root'})` for new singleton services (Angular v22+)
- Use the `inject()` function instead of constructor injection
```

[Click here to download the best-practices.md file.](/assets/context/best-practices.md)

## [Rules Files](#rules-files)

Several tools, such as [Antigravity](https://antigravity.google?utm_source=adev\&utm_medium=website\&utm_campaign=BUILD_WITH_AI_ANGULAR\&utm_term=angular_devrel\&utm_content=build_with_ai_angular_antigravity) have rules files useful for providing critical context to LLMs.

| Environment/IDE      | Rules File                                               | Installation Instructions                                                                                                            |
| :------------------- | :------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
| Antigravity          | [GEMINI.md](/assets/context/GEMINI.md)                   | [Configure `GEMINI.md`](https://antigravity.google/docs/rules-workflows)                                                             |
| Copilot powered IDEs | [copilot-instructions.md](/assets/context/guidelines.md) | [Configure `.github/copilot-instructions.md`](https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions) |
| Cursor               | [cursor.md](/assets/context/angular-20.mdc)              | [Configure `cursorrules.md`](https://docs.cursor.com/context/rules)                                                                  |
| JetBrains IDEs       | [AGENTS.md](/assets/context/AGENTS.md)                   | [Configure `AGENTS.md`](https://junie.jetbrains.com/docs/guidelines-and-memory.html#how-junie-cli-discovers-guidelines)              |
| VS Code              | [.instructions.md](/assets/context/guidelines.md)        | [Configure `.instructions.md`](https://code.visualstudio.com/docs/copilot/copilot-customization#_custom-instructions)                |
| Windsurf             | [guidelines.md](/assets/context/guidelines.md)           | [Configure `guidelines.md`](https://docs.windsurf.com/windsurf/cascade/memories#rules)                                               |

## [Angular CLI MCP Server setup](#angular-cli-mcp-server-setup)

The Angular CLI includes an experimental [Model Context Protocol (MCP) server](https://modelcontextprotocol.io/) that allows AI assistants in your development environment to interact with the Angular CLI.

[**Learn how to set up the Angular CLI MCP Server**](/ai/mcp)

## [Providing Context with `llms.txt`](#providing-context-with-llmstxt)

`llms.txt` is a proposed standard for websites designed to help LLMs better understand and process their content. The Angular team has developed two versions of this file to help LLMs and tools that use LLMs for code generation to create better modern Angular code.

* [llms.txt](/llms.txt) - an index file providing links to key files and resources.
* [llms-full.txt](/assets/context/llms-full.txt) - a more robust compiled set of resources describing how Angular works and how to build Angular applications.

Be sure to check out the [overview page](/ai) for more information on how to integrate AI into your Angular applications.

----
url: https://angular.dev/api/forms/signals/TreeValidationResult
----

```
    
      type TreeValidationResult<E extends ValidationError.WithOptionalFieldTree = ValidationError.WithOptionalFieldTree> = ValidationSuccess | OneOrMany<E>
    
    
```

## [Description](#description)

The result of running a tree validation function.

The result may be one of the following:

1. A [`ValidationSuccess`](/api/forms/signals/ValidationSuccess) to indicate no errors.
2. A [`ValidationError`](/api/forms/signals/ValidationError) without a field to indicate an error on the field being validated.
3. A [`ValidationError`](/api/forms/signals/ValidationError) with a field to indicate an error on the target field.
4. A list of [`ValidationError`](/api/forms/signals/ValidationError) with or without fields to indicate multiple errors.

Jump to details

----
url: https://angular.dev/api/router/CanActivateChildFn
----

```
    
      type CanActivateChildFn = (  childRoute: ActivatedRouteSnapshot,  state: RouterStateSnapshot,) => MaybeAsync<GuardResult>
    
    
```

## [Description](#description)

The signature of a function used as a `canActivateChild` guard on a [`Route`](/api/router/Route).

If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), the current navigation is cancelled and a new navigation begins to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements a `canActivate` function that checks whether the current user has permission to activate the requested route.

```
const canActivateChildExample: CanActivateChildFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  return inject(PermissionsService).canActivate(inject(UserToken), route.params['id']);
};

bootstrapApplication(App, {
  providers: [
    provideRouter([
      {
        path: 'team/:id',
        component: TeamComponent,
        canActivateChild: [canActivateChildExample],
        children: [],
      },
    ]),
  ],
});
```

Jump to details

----
url: https://angular.dev/api/core/AnimationFunction
----

```
    
      type AnimationFunction = (event: AnimationCallbackEvent) => void
    
    
```

Jump to details

----
url: https://angular.dev/api/router/withAutoCleanupInjectors
----

```
function withAutoCleanupInjectors(): AutoCleanupInjectorsFeature;
```

@returns`AutoCleanupInjectorsFeature`

## [Description](#description)

When enabled, the router will automatically destroy [`EnvironmentInjector`](/api/core/EnvironmentInjector)s associated with [`Route`](/api/router/Route)s that are no longer active or stored by the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy).

This feature is opt-in and requires [`RouteReuseStrategy.shouldDestroyInjector`](/api/router/RouteReuseStrategy#shouldDestroyInjector) to return `true` for the routes that should be destroyed. If the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) uses stored handles, it should also implement `retrieveStoredRouteHandles` to ensure injectors for handles that will be reattached are not destroyed.

Jump to details

----
url: https://angular.dev/cli/extract-i18n
----

```
ng extract-i18n [project] [options]
```

Extracts i18n messages from source code.

## [Arguments](#arguments)

`project`

The name of the project to build. Can be an application or a library.

Value Type`string`

## [Options](#options)

`build-target`

A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.

Value Type`string`

`configuration`

Alias`c`

One or more named builder configurations as a comma-separated list as specified in the "configurations" section in angular.json. The builder uses the named configurations to run the given target. For more information, see [https://angular.dev/reference/configs/workspace-config#alternate-build-configurations](/reference/configs/workspace-config#alternate-build-configurations).

Value Type`string`

`format`

Output format for the generated file.

Value Type`string`Allowed Values`arb`, `json`, `legacy-migrate`, `xlf`, `xlf2`, `xlif`, `xliff`, `xliff2`, `xmb`Default`xlf`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`i18n-duplicate-translation`

How to handle duplicate translations.

Value Type`string`Allowed Values`error`, `ignore`, `warning`

`out-file`

Name of the file to output.

Value Type`string`

`output-path`

Path where output will be placed.

Value Type`string`

`progress`

Log progress to the console.

Value Type`boolean`Default`true`

Jump to details

----
url: https://angular.dev/cli/update
----

```
ng update [packages..] [options]
```

Perform a basic update to the current stable release of the core framework and CLI by running the following command.

```
ng update @angular/cli @angular/core
```

To update to the next beta or pre-release version, use the `--next` option.

To update from one major version to another, use the format

```
ng update @angular/cli@^<major_version> @angular/core@^<major_version>
```

We recommend that you always update to the latest patch version, as it contains fixes we released since the initial major release. For example, use the following command to take the latest 21.x.x version and use that to update.

```
ng update @angular/cli@^21 @angular/core@^21
```

For detailed information and guidance on updating your application, see the interactive [Angular Update Guide](/update-guide).

## [Arguments](/cli/update#arguments)

`packages`

The names of package(s) to update.

Value Type`array`Default

## [Options](/cli/update#options)

`allow-dirty`

Whether to allow updating when the repository contains modified or untracked files.

Value Type`boolean`Default`false`

`create-commits`

Alias`C`

Create source control commits for updates and migrations.

Value Type`boolean`Default`false`

`force`

Ignore peer dependency version mismatches.

Value Type`boolean`Default`false`

`from`

Version from which to migrate from. Only available when a single package is updated, and only with 'migrate-only'.

Value Type`string`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`migrate-only`

Only perform a migration, do not update the installed version.

Value Type`boolean`

`name`

The name of the migration to run. Only available when a single package is updated.

Value Type`string`

`next`

Use the prerelease version, including beta and RCs.

Value Type`boolean`Default`false`

`to`

Version up to which to apply migrations. Only available when a single package is updated, and only with 'migrate-only' option. Requires 'from' to be specified. Default to the installed version detected.

Value Type`string`

`verbose`

Display additional details about internal operations during execution.

Value Type`boolean`Default`false`

Jump to details

----
url: https://angular.dev/api/cdk/testing/TestElement
----

```
    
      interface TestElement {
}
    
    
```

Blur the element.

@returns`Promise<void>`

Clear the element's input (for input and textarea elements only).

@returns`Promise<void>`

Click the element at the default location for the current environment. If you need to guarantee the element is clicked at a specific location, consider using `click('center')` or `click(x, y)` instead.

@parammodifiers`ModifierKeys | undefined`

@returns`Promise<void>`

Click the element at the element's center.

@paramlocation`"center"`

@parammodifiers`ModifierKeys | undefined`

@returns`Promise<void>`

Click the element at the specified coordinates relative to the top-left of the element.

@paramrelativeX`number`

Coordinate within the element, along the X-axis at which to click.

@paramrelativeY`number`

Coordinate within the element, along the Y-axis at which to click.

@parammodifiers`ModifierKeys | undefined`

@parammodifiers`ModifierKeys | undefined`

Sends the given string to the input as a series of key presses. Also fires input events and attempts to add the string to the Element's value. Note that some environments cannot reproduce native browser behavior for keyboard shortcuts such as Tab, Ctrl + A, etc.

@paramkeys`(string | TestKey)[]`

@returns`Promise<void>`

Sends the given string to the input as a series of key presses. Also fires input events and attempts to add the string to the Element's value.

@parammodifiers`ModifierKeys`

@paramkeys`(string | TestKey)[]`

@returns`Promise<void>`

Gets the text from the element.

@paramoptions`TextOptions | undefined`

Checks whether this element matches the given selector.

@paramselector`string`

@returns`Promise<boolean>`

Checks whether the element is focused.

@returns`Promise<boolean>`

Sets the value of a property of an input.

@paramvalue`string`

@returns`Promise<void>`

Selects the options at the specified indexes inside of a native `select` element.

@paramoptionIndexes`number[]`

@returns`Promise<void>`

Dispatches an event with a particular name.

@paramname`string`

Name of the event to be dispatched.

@paramdata`Record<string, EventData> | undefined`

@returns`Promise<void>`

Jump to details

----
url: https://angular.dev/api/common/NgLocalization
----

```
    
      abstract class NgLocalization {}
    
    
```

@paramvalue`number`

@paramlocale`string | undefined`

@returns`string`

Jump to details

----
url: https://angular.dev/api/common/getLocaleDateTimeFormat
----

Angular recommends relying on the `Intl` API for i18n. Use `Intl.DateTimeFormat` for date formating instead.

## [API](#api)

```
function getLocaleDateTimeFormat(locale: string, width: FormatWidth): string;
```

----
url: https://angular.dev/api/common/http/HttpXhrBackend
----

```
    
      class HttpXhrBackend implements HttpBackend {}
    
    
```

@paramxhrFactory`XhrFactory`

@returns`HttpXhrBackend`

Processes a request and returns a stream of response events.

@paramreq`HttpRequest<any>`

The request object.

@returns`Observable<HttpEvent<any>>`

An observable of the response events.

Jump to details

----
url: https://angular.dev/api/core/KeyValueChanges
----

```
    
      interface KeyValueChanges<K, V> {}
    
    
```

Iterate over all changes. [`KeyValueChangeRecord`](/api/core/KeyValueChangeRecord) will contain information about changes to each item.

@paramfn`(r: KeyValueChangeRecord<K, V>) => void`

@returns`void`

Iterate over changes in the order of original Map showing where the original items have moved.

@paramfn`(r: KeyValueChangeRecord<K, V>) => void`

@returns`void`

Iterate over all keys for which values have changed.

@paramfn`(r: KeyValueChangeRecord<K, V>) => void`

@returns`void`

Iterate over all added items.

@paramfn`(r: KeyValueChangeRecord<K, V>) => void`

@returns`void`

Iterate over all removed items.

@paramfn`(r: KeyValueChangeRecord<K, V>) => void`

@returns`void`

Jump to details

----
url: https://angular.dev/api/platform-browser/DomSanitizer
----

```
    
      abstract class DomSanitizer implements Sanitizer {}
    
    
```

Gets a safe value from either a known safe value or a value with unknown safety.

If the given value is already a [`SafeValue`](/api/platform-browser/SafeValue), this method returns the unwrapped value. If the security context is HTML and the given value is a plain string, this method sanitizes the string, removing any potentially unsafe content. For any other security context, this method throws an error if provided with a plain string.

@paramcontext`SecurityContext`

@paramvalue`string | SafeValue | null`

@returns`string | null`

Bypass security and trust the given value to be safe HTML. Only use this when the bound HTML is unsafe (e.g. contains `<script>` tags) and the code should be executed. The sanitizer will leave safe HTML intact, so in most situations this method should not be used.

**WARNING:** calling this method with untrusted user data exposes your application to XSS security risks!

@paramvalue`string`

@returns`SafeHtml`

Bypass security and trust the given value to be safe style value (CSS).

**WARNING:** calling this method with untrusted user data exposes your application to XSS security risks!

@paramvalue`string`

@returns`SafeStyle`

Bypass security and trust the given value to be safe JavaScript.

**WARNING:** calling this method with untrusted user data exposes your application to XSS security risks!

@paramvalue`string`

@returns`SafeScript`

Bypass security and trust the given value to be a safe style URL, i.e. a value that can be used in hyperlinks or `<img src>`.

**WARNING:** calling this method with untrusted user data exposes your application to XSS security risks!

@paramvalue`string`

@returns`SafeUrl`

Bypass security and trust the given value to be a safe resource URL, i.e. a location that may be used to load executable code from, like `<script src>`, or `<iframe src>`.

**WARNING:** calling this method with untrusted user data exposes your application to XSS security risks!

@paramvalue`string`

@returns`SafeResourceUrl`

## [Description](#description)

DomSanitizer helps preventing Cross Site Scripting Security bugs (XSS) by sanitizing values to be safe to use in the different DOM contexts.

For example, when binding a URL in an `<a [href]="someValue">` hyperlink, `someValue` will be sanitized so that an attacker cannot inject e.g. a `javascript:` URL that would execute code on the website.

In specific situations, it might be necessary to disable sanitization, for example if the application genuinely needs to produce a `javascript:` style link with a dynamic value in it. Users can bypass security by constructing a value with one of the `bypassSecurityTrust...` methods, and then binding to that value from the template.

These situations should be very rare, and extraordinary care must be taken to avoid creating a Cross Site Scripting (XSS) security bug!

When using `bypassSecurityTrust...`, make sure to call the method as early as possible and as close as possible to the source of the value, to make it easy to verify no security bug is created by its use.

It is not required (and not recommended) to bypass security if the value is safe, e.g. a URL that does not start with a suspicious protocol, or an HTML snippet that does not contain dangerous code. The sanitizer leaves safe values intact.

Jump to details

----
url: https://angular.dev/api/platform-browser/disableDebugTools
----

```
function disableDebugTools(): void;
```

@returns`void`

Jump to details

----
url: https://angular.dev/api/router/Resolve
----

```
    
      interface Resolve<T> {}
    
    
```

@paramroute`ActivatedRouteSnapshot`

@paramstate`RouterStateSnapshot`

@returns`MaybeAsync<T | RedirectCommand>`

## [Description](#description)

Interface that classes can implement to be a data provider. A data provider class can be used with the router to resolve data during navigation. The interface defines a `resolve()` method that is invoked right after the [`ResolveStart`](/api/router/ResolveStart) router event. The router waits for the data to be resolved before the route is finally activated.

The following example implements a `resolve()` method that retrieves the data needed to activate the requested route.

```
@Injectable({ providedIn: 'root' })
export class HeroResolver implements Resolve<Hero> {
  private readonly service = inject(HeroService);

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<Hero>|Promise<Hero>|Hero {
    return this.service.getHero(route.paramMap.get('id'));
  }
}
```

Here, the defined `resolve()` function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'detail/:id',
        component: HeroDetailComponent,
        resolve: {
          hero: HeroResolver
        }
      }
    ])
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}
```

And you can access to your resolved data from `HeroComponent`:

```
@Component({
 selector: "app-hero",
 templateUrl: "hero.component.html",
})
export class HeroComponent {

  private readonly activatedRoute = inject(ActivatedRoute);

 ngOnInit() {
   this.activatedRoute.data.subscribe(({ hero }) => {
     // do something with your resolved data ...
   })
 }

}
```

## [Usage Notes](#usage-notes)

When both guard and resolvers are specified, the resolvers are not executed until all guards have run and succeeded. For example, consider the following route configuration:

```
{
 path: 'base'
 canActivate: [BaseGuard],
 resolve: {data: BaseDataResolver}
 children: [
  {
    path: 'child',
    guards: [ChildGuard],
    component: ChildComponent,
    resolve: {childData: ChildDataResolver}
   }
 ]
}
```

The order of execution is: BaseGuard, ChildGuard, BaseDataResolver, ChildDataResolver.

Jump to details

----
url: https://angular.dev/api/common/NumberSymbol
----

[`getLocaleNumberSymbol`](/api/common/getLocaleNumberSymbol) is deprecated

## [API](#api)

```
    
      enum NumberSymbol {}
    
    
```

Decimal separator. For `en-US`, the dot character. Example: 2,345`.`67

Grouping separator, typically for thousands. For `en-US`, the comma character. Example: 2`,`345.67

List-item separator. Example: "one, two, and three"

Sign for percentage (out of 100). Example: 23.4%

Sign for positive numbers. Example: +23

Sign for negative numbers. Example: -23

Computer notation for exponential value (n times a power of 10). Example: 1.2E3

Human-readable format of exponential. Example: 1.2x103

Sign for permille (out of 1000). Example: 23.4‰

Infinity, can be used with plus and minus. Example: ∞, +∞, -∞

Not a number. Example: NaN

Symbol used between time units. Example: 10:52

Decimal separator for currency values (fallback to `Decimal`). Example: $2,345.67

Group separator for currency values (fallback to `Group`). Example: $2,345.67

Jump to details

----
url: https://angular.dev/tutorials/signals/10-reacting-to-signal-changes-with-effect
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/9-query-child-elements-with-signal-queries)[](/tutorials/signals/11-next-steps)

[](/tutorials/signals/9-query-child-elements-with-signal-queries)[](/tutorials/signals/11-next-steps)

Now that you've learned [querying child elements with signal queries](/tutorials/signals/9-query-child-elements-with-signal-queries), let's explore how to react to signal changes with effects. Effects are functions that run automatically when their dependencies change, making them perfect for side effects like logging, DOM manipulation, or API calls.

**Important: Effects should be the last API you reach for.** Always prefer [`computed()`](/api/core/computed) for derived values and [`linkedSignal()`](/api/core/linkedSignal) for values that can be both derived and manually set. If you find yourself copying data from one signal to another with an effect, it's a sign you should move your source-of-truth higher up and use [`computed()`](/api/core/computed) or [`linkedSignal()`](/api/core/linkedSignal) instead. Effects are best for syncing signal state to imperative, non-signal APIs.

In this activity, you'll learn how to use the [`effect()`](/api/core/effect) function appropriately for legitimate side effects that respond to signal changes.

***

You have a theme manager app with signals already set up. Now you'll add effects to automatically react to signal changes.

1. ### [Import effect function](#import-effect-function)

   Add [`effect`](/api/core/effect) to your existing imports.

   ```
   // Add effect to existing imports
   import {Component, signal, computed, effect, ChangeDetectionStrategy} from '@angular/core';
   ```

   The [`effect`](/api/core/effect) function creates a reactive side effect that runs automatically when any signals it reads change.

2. ### [Create an effect for local storage](#create-an-effect-for-local-storage)

   Add an effect that automatically saves the theme to local storage when it changes.

   ```
   constructor() {
     // Save theme to localStorage whenever it changes
     effect(() => {
       localStorage.setItem('theme', this.theme());
       console.log('Theme saved to localStorage:', this.theme());
     });
   }
   ```

   This effect runs whenever the theme signal changes, automatically persisting the user's preference.

3. ### [Create an effect for logging user activity](#create-an-effect-for-logging-user-activity)

   Add an effect that logs when the user logs in or out.

   ```
   constructor() {
     // ... previous effect

     // Log user activity changes
     effect(() => {
       const status = this.isLoggedIn() ? 'logged in' : 'logged out';
       const user = this.username();
       console.log(`User ${user} is ${status}`);
     });
   }
   ```

   This effect demonstrates how effects can read multiple signals and react to changes in any of them.

4. ### [Create an effect with cleanup](#create-an-effect-with-cleanup)

   Add an effect that sets up a timer and cleans up when the component is destroyed.

   ```
   constructor() {
     // ... previous effects

     // Timer effect with cleanup
     effect((onCleanup) => {
       const interval = setInterval(() => {
         console.log('Timer tick - Current theme:', this.theme());
       }, 5000);

       // Clean up the interval when the effect is destroyed
       onCleanup(() => {
         clearInterval(interval);
         console.log('Timer cleaned up');
       });
     });
   }
   ```

   This effect reads no signals while it runs, because `this.theme()` is only called later inside the `setInterval` callback. It runs once and never re-runs, so the cleanup only happens when the component is destroyed, for example when the preview reloads.

5. ### [Test the effects](#test-the-effects)

   Open the browser console and interact with the app:

   * **Toggle Theme** - See localStorage saves
   * **Login/Logout** - See user activity logging
   * **Watch Timer** - See periodic theme logging every 5 seconds

   Effects run once when the component is created, so some of these logs appear before you click anything. After that, they run again whenever the signals they read change.

Excellent! You've now learned how to use effects with signals. Key concepts to remember:

* **Effects are reactive**: They automatically run when any signal they read changes
* **Side effects only**: Perfect for logging, DOM manipulation, API calls, and syncing to imperative APIs
* **Cleanup**: Use the `onCleanup` callback to clean up resources like timers or subscriptions
* **Automatic tracking**: Effects automatically track which signals they read and re-run when those signals change

**Remember: Use effects sparingly!** The examples in this lesson (localStorage sync, logging, timers) are appropriate uses. Avoid effects for:

* Deriving values from other signals - use [`computed()`](/api/core/computed) instead
* Creating writable derived state - use [`linkedSignal()`](/api/core/linkedSignal) instead
* Copying data between signals - restructure to use a shared source of truth

Effects are powerful but should be your last resort when [`computed()`](/api/core/computed) and [`linkedSignal()`](/api/core/linkedSignal) can't solve your use case.

----
url: https://angular.dev/api/platform-browser/SafeScript
----

```
    
      interface SafeScript extends SafeValue {}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/NgFor
----

Use the `@for` block instead. Intent to remove in a future major release

## [API](#api)

```
    
      class NgFor<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {}
    
    
```

```
<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
```

The shorthand form expands into a long form that uses the `ngForOf` selector on an `<ng-template>` element. The content of the `<ng-template>` element is the `<li>` element that held the short-form directive.

Here is the expanded version of the short-form example.

```
<ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
  <li>...</li>
</ng-template>
```

```
<li *ngFor="let user of users; index as i; first as isFirst">
   {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>
```

----
url: https://angular.dev/api/platform-browser-dynamic/platformBrowserDynamic
----

Use the [`platformBrowser`](/api/platform-browser/platformBrowser) function instead from `@angular/platform-browser`. In case you are not in a CLI app and rely on JIT compilation, you will also need to import `@angular/compiler`

## [API](#api)

```
    
      const platformBrowserDynamic: (extraProviders?: StaticProvider[] | undefined) => PlatformRef;
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/animations/BrowserAnimationsModule
----

```
    
      class BrowserAnimationsModule {}
    
    
```

Configures the module based on the specified object.

@paramconfig`BrowserAnimationsModuleConfig`

Object used to configure the behavior of the [`BrowserAnimationsModule`](/api/platform-browser/animations/BrowserAnimationsModule).

@returns`ModuleWithProviders<BrowserAnimationsModule>`

Usage notes

When registering the [`BrowserAnimationsModule`](/api/platform-browser/animations/BrowserAnimationsModule), you can use the `withConfig` function as follows:

```
@NgModule({
  imports: [BrowserAnimationsModule.withConfig(config)]
})
class MyNgModule {}
```

Jump to details

----
url: https://angular.dev/api/aria/tree/TreeItemGroup
----

```
    
      class TreeItemGroup<V> implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

Tree item that owns the group.

@returns`void`

@returns`void`

## [Description](#description)

Group that contains children tree items.

The `ngTreeItemGroup` structural directive should be applied to an `ng-template` that wraps the child `ngTreeItem` elements. It is used to define a group of children for an expandable `ngTreeItem`. The `ownedBy` input links the group to its parent `ngTreeItem`.

```
<li ngTreeItem [value]="'parent-id'">
  Parent Item
  <ul role="group">
    <ng-template ngTreeItemGroup [ownedBy]="parentTreeItemRef">
      <li ngTreeItem [value]="'child-id'">Child Item</li>
    </ng-template>
  </ul>
</li>
```

Jump to details

----
url: https://angular.dev/api/core/@switch
----

The `@switch` block is inspired by the JavaScript `switch` statement:

## [Syntax](#syntax)

```
@switch (condition) {
  @case (caseA) {
    Case A.
  }
  @case (caseB)
  @case (caseC) {
    Case B or C.
  }
  @default {
    Default case.
  }
}
```

## [Description](#description)

The `@switch` blocks displays content selected by one of the cases matching against the conditional expression. The value of the conditional expression is compared to the case expression using the `===` operator.

The `@default` block is optional and can be omitted. If no `@case` matches the expression and there is no `@default` block, nothing is shown.

You can specify multiple conditions for a single block by having consecutive `@case(...)` statements.

**`@switch` does not have fallthrough**, so you do not need an equivalent to a `break` or `return` statement.

### [Exhaustive type checking](#exhaustive-type-checking)

`@switch` supports exhaustive type checking, allowing Angular to verify at compile time that all possible values of a union type are handled.

By using `@default never;`, you explicitly declare that no remaining cases should exist. If the union type is later extended and a new case is not covered by an @case, Angular’s template type checker will report an error, helping you catch missing branches early.

**NOTE:** Exhaustiveness checking relies on TypeScript's type narrowing, which only works on variables. It will not work if the switch condition is a function call or a signal (for example, `@switch (state())`). To work around this, assign the signal to a `@let` variable, e.g.: `@let mySignal = this.mySignal()`.

```
@Component({
  template: `
    @switch (state) {
      @case ('loggedOut') {
        <button>Login</button>
      }

      @case ('loggedIn') {
        <p>Welcome back!</p>
      }

      @default never; // throws because `@case ('loading')` is missing
    }
  `,
})
export class AppComponent {
  state: 'loggedOut' | 'loading' | 'loggedIn' = 'loggedOut';
}
```

When the switched expression is nested within a union, you must explicitly specify the expression to check for exhaustiveness.

```
@Component({
  template: `
    @switch (state.mode) {
      @case ('show') {
        {{ state.menu }}
      }
      @case ('hide') {}
      @default never;
    }
  `,
})
export class App {
  state!: {mode: 'hide'} | {mode: 'show'; menu: number};
}
```

Jump to details

----
url: https://angular.dev/api/core/isWritableSignal
----

```
function isWritableSignal(value: unknown): value is WritableSignal<unknown>;
```

@paramvalue`unknown`

@returns`value is WritableSignal<unknown>`

Jump to details

----
url: https://angular.dev/api/forms/FormGroupName
----

## [API](/api/forms/FormGroupName#api)

```
    
      class FormGroupName extends AbstractFormGroupDirective implements OnInit ,OnDestroy {}
    
    
```

@paramparent`ControlContainer`

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@returns`FormGroupName`

Tracks the name of the [`FormGroup`](/api/forms/FormGroup) bound to the directive. The name corresponds to a key in the parent [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray). Accepts a name as a string or a number. The name in the form of a string is useful for individual forms, while the numerical form allows for form groups to be bound to indices when iterating over groups in a [`FormArray`](/api/forms/FormArray).

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](/api/forms/FormGroupName#description)

Syncs a nested [`FormGroup`](/api/forms/FormGroup) or [`FormRecord`](/api/forms/FormRecord) to a DOM element.

This directive can only be used with a parent [`FormGroupDirective`](/api/forms/FormGroupDirective).

It accepts the string name of the nested [`FormGroup`](/api/forms/FormGroup) or [`FormRecord`](/api/forms/FormRecord) to link, and looks for a [`FormGroup`](/api/forms/FormGroup) or [`FormRecord`](/api/forms/FormRecord) registered with that name in the parent [`FormGroup`](/api/forms/FormGroup) instance you passed into [`FormGroupDirective`](/api/forms/FormGroupDirective).

Use nested form groups to validate a sub-group of a form separately from the rest or to group the values of certain controls into their own nested object.

## [Exported by](/api/forms/FormGroupName#exported-by)

* `ReactiveFormsModule`

## [Usage Notes](/api/forms/FormGroupName#usage-notes)

### [Access the group by name](/api/forms/FormGroupName#access-the-group-by-name)

The following example uses the [`AbstractControl.get`](/api/forms/AbstractControl#get) method to access the associated [`FormGroup`](/api/forms/FormGroup)

```
this.form.get('name');
```

### [Access individual controls in the group](/api/forms/FormGroupName#access-individual-controls-in-the-group)

The following example uses the [`AbstractControl.get`](/api/forms/AbstractControl#get) method to access individual controls within the group using dot syntax.

```
this.form.get('name.first');
```

### [Register a nested ``](/api/forms/FormGroupName#register-a-nested-formgroup)`FormGroup`.

The following example registers a nested *name* [`FormGroup`](/api/forms/FormGroup) within an existing [`FormGroup`](/api/forms/FormGroup), and provides methods to retrieve the nested [`FormGroup`](/api/forms/FormGroup) and individual controls.

```
import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      @if (name.invalid) {
        <p>Name is invalid.</p>
      }
      <div formGroupName="name">
        <input formControlName="first" placeholder="First name" />
        <input formControlName="last" placeholder="Last name" />
      </div>
      <input formControlName="email" placeholder="Email" />
      <button type="submit">Submit</button>
    </form>

    <button (click)="setPreset()">Set preset</button>
  `,
  standalone: false,
})
export class NestedFormGroupComp {
  form = new FormGroup({
    name: new FormGroup({
      first: new FormControl('Nancy', Validators.minLength(2)),
      last: new FormControl('Drew', Validators.required),
    }),
    email: new FormControl(),
  });

  get first(): any {
    return this.form.get('name.first');
  }

  get name(): any {
    return this.form.get('name');
  }

  onSubmit() {
    console.log(this.first.value); // 'Nancy'
    console.log(this.name.value); // {first: 'Nancy', last: 'Drew'}
    console.log(this.form.value); // {name: {first: 'Nancy', last: 'Drew'}, email: ''}
    console.log(this.form.status); // VALID
  }

  setPreset() {
    this.name.setValue({first: 'Bess', last: 'Marvin'});
  }
}
```

Jump to details

----
url: https://angular.dev/tutorials/deferrable-views/1-what-are-deferrable-views
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/deferrable-views/2-loading-error-placeholder)

[](/tutorials/deferrable-views/2-loading-error-placeholder)

A fully rendered Angular page may contain many different components, directives, and pipes. While certain parts of the page should be shown to the user immediately, there may be portions that can wait to display until later. Angular's *deferrable views*, using the `@defer` syntax, can help you speed up your application by telling Angular to wait to download the JavaScript for the parts of the page that don't need to be shown right away.

In this activity, you'll learn how to use deferrable views to defer load a section of your component template.

***

1. ### [Add a `@defer` block to a section of a template](#add-a-defer-block-to-a-section-of-a-template)

   In your `app.ts`, wrap the `article-comments` component with a `@defer` block to defer load it.

   ```
   @defer {
     <article-comments />
   }
   ```

   By default, `@defer` loads the `article-comments` component when the browser is idle.

   In your browser's developer console, you can see that the `article-comments-component` lazy chunk file is loaded separately (The specific file names may change from run to run):

   ```
   Initial chunk files | Names | Raw size
   chunk-NNSQHFIE.js | - | 769.00 kB |
   main.js | main | 229.25 kB |

   Lazy chunk files | Names | Raw size
   chunk-T5UYXUSI.js | article-comments-component | 1.84 kB |
   ```

Great work! You’ve learned the basics of deferrable views.

----
url: https://angular.dev/reference/migrations/outputs
----

Angular introduced an improved API for outputs in v17.3 that is considered production ready as of v19. This API mimics the [`input()`](/api/core/input) API but is not based on Signals. Read more about custom events output function and its benefits in the [dedicated guide](guide/components/outputs).

To support existing projects that would like to use output function, the Angular team provides an automated migration that converts [`@Output`](/api/core/Output) custom events to the new [`output()`](/api/core/output) API.

Run the schematic using the following command:

```
ng generate @angular/core:output-migration
```

## [What does the migration change?](#what-does-the-migration-change)

1. [`@Output()`](/api/core/Output) class members are updated to their [`output()`](/api/core/output) equivalent.
2. Imports in the file of components or directives, at TypeScript module level, are updated as well.
3. Migrates API calls like `event.next()`, whose use is not recommended, to `event.emit()` and removes `event.complete()` calls.

**Before**

```
import {Component, Output, EventEmitter} from '@angular/core';

@Component({
  template: `<button (click)="someMethod('test')">emit</button>`,
})
export class MyComponent {
  @Output() someChange = new EventEmitter<string>();

  someMethod(value: string): void {
    this.someChange.emit(value);
  }
}
```

**After**

```
import {Component, output} from '@angular/core';

@Component({
  template: `<button (click)="someMethod('test')">emit</button>`,
})
export class MyComponent {
  readonly someChange = output<string>();

  someMethod(value: string): void {
    this.someChange.emit(value);
  }
}
```

## [Configuration options](#configuration-options)

The migration supports a few options for fine tuning the migration to your specific needs.

### [`--path`](#--path)

If not specified, the migration will ask you for a path and update your whole Angular CLI workspace. You can limit the migration to a specific sub-directory using this option.

### [`--analysis-dir`](#--analysis-dir)

In large projects you may use this option to reduce the amount of files being analyzed. By default, the migration analyzes the whole workspace, regardless of the `--path` option, in order to update all references affected by an [`@Output()`](/api/core/Output) migration.

With this option, you can limit analysis to a sub-folder. Note that this means that any references outside this directory are silently skipped, potentially breaking your build.

Use these options as shown below:

```
ng generate @angular/core:output-migration --path src/app/sub-folder
```

## [Exceptions](#exceptions)

In some cases, the migration will not touch the code. One of these exceptions is the case where the event is used with a `pipe()` method outside a test file. The following code won't be migrated:

```
export class MyDialogComponent {
  @Output() close = new EventEmitter<void>();
  doSome(): void {
    this.close.complete();
  }
  otherThing(): void {
    this.close.pipe();
  }
}
```

In a test file the migration rewrites the usage instead of skipping the output, so `this.close.pipe(…)` becomes `outputToObservable(this.close).pipe(…)`.

**NOTE:** The migration recognizes a test file by looking for an import whose path contains `jasmine` or `catalyst`. It does not recognize a file that only imports Jest or Vitest, so it skips the output there instead of rewriting it.

----
url: https://angular.dev/api/router/CanDeactivate
----

```
    
      interface CanDeactivate<T> {}
    
    
```

@paramcomponent`T`

@paramcurrentRoute`ActivatedRouteSnapshot`

@paramcurrentState`RouterStateSnapshot`

@paramnextState`RouterStateSnapshot`

@returns`MaybeAsync<GuardResult>`

## [Description](#description)

Interface that a class can implement to be a guard deciding if a route can be deactivated. If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), current navigation is cancelled and a new navigation begins to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements a [`CanDeactivate`](/api/router/CanDeactivate) function that checks whether the current user has permission to deactivate the requested route.

```
class UserToken {}
class Permissions {
  canDeactivate(user: UserToken, id: string): boolean {
    return true;
  }
}
```

Here, the defined guard function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
@Injectable()
class CanDeactivateTeam implements CanDeactivate<TeamComponent> {
  private readonly permissions = inject(Permissions);
  private readonly currentUser = inject(UserToken);

  canDeactivate(
    component: TeamComponent,
    currentRoute: ActivatedRouteSnapshot,
    currentState: RouterStateSnapshot,
    nextState: RouterStateSnapshot
  ): MaybeAsync<GuardResult> {
    return this.permissions.canDeactivate(this.currentUser, route.params.id);
  }
}

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'team/:id',
        component: TeamComponent,
        canDeactivate: [CanDeactivateTeam]
      }
    ])
  ],
  providers: [CanDeactivateTeam, UserToken, Permissions]
})
class AppModule {}
```

Jump to details

----
url: https://angular.dev/guide/testing/component-harnesses-testing-environments
----

### [When does adding support for a test environment make sense?](#when-does-adding-support-for-a-test-environment-make-sense)

To use component harnesses in the following environments, you can use Angular CDK's two built-in environments:

* Unit tests
* WebDriver end-to-end tests

To use a supported testing environment, read the [Creating harnesses for your components guide](guide/testing/creating-component-harnesses).

Otherwise, to add support for other environments, you need to define how to interact with a DOM element and how DOM interactions work in your environment. Continue reading to learn more.

### [CDK Installation](#cdk-installation)

The [Component Dev Kit (CDK)](https://material.angular.dev/cdk/categories) is a set of behavior primitives for building components. To use the component harnesses, first install `@angular/cdk` from npm. You can do this from your terminal using the Angular CLI:

```
ng add @angular/cdk
```

## [Creating a `TestElement` implementation](#creating-a-testelement-implementation)

Every test environment must define a [`TestElement`](/api/cdk/testing/TestElement) implementation. The [`TestElement`](/api/cdk/testing/TestElement) interface serves as an environment-agnostic representation of a DOM element. It enables harnesses to interact with DOM elements regardless of the underlying environment. Because some environments don't support interacting with DOM elements synchronously (e.g. WebDriver), all [`TestElement`](/api/cdk/testing/TestElement) methods are asynchronous, returning a `Promise` with the result of the operation.

[`TestElement`](/api/cdk/testing/TestElement) offers a number of methods to interact with the underlying DOM such as `blur()`, `click()`, `getAttribute()`, and more. See the [TestElement API reference page](/api/cdk/testing/TestElement) for the full list of methods.

The [`TestElement`](/api/cdk/testing/TestElement) interface consists largely of methods that resemble methods available on `HTMLElement`. Similar methods exist in most test environments, which makes implementing the methods fairly straightforward. However, one important difference to note when implementing the `sendKeys` method, is that the key codes in the [`TestKey`](/api/cdk/testing/TestKey) enum likely differ from the key codes used in the test environment. Environment authors should maintain a mapping from [`TestKey`](/api/cdk/testing/TestKey) codes to the codes used in the particular testing environment.

The [UnitTestElement](/api/cdk/testing/testbed/UnitTestElement) and [SeleniumWebDriverElement](/api/cdk/testing/selenium-webdriver/SeleniumWebDriverElement) implementations in Angular CDK serve as good examples of implementations of this interface.

## [Creating a `HarnessEnvironment` implementation](#creating-a-harnessenvironment-implementation)

Test authors use [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) to create component harness instances for use in tests. [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) is an abstract class that must be extended to create a concrete subclass for the new environment. When supporting a new test environment, create a [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) subclass that adds concrete implementations for all abstract members.

[`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) has a generic type parameter: `HarnessEnvironment<E>`. This parameter, `E`, represents the raw element type of the environment. For example, this parameter is Element for unit test environments.

The following are the abstract methods that must be implemented:

| Method                                                       | Description                                                                                                                                                                                                                                                            |
| :----------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `abstract getDocumentRoot(): E`                              | Gets the root element for the environment (e.g. `document.body`).                                                                                                                                                                                                      |
| `abstract createTestElement(element: E): TestElement`        | Creates a [`TestElement`](/api/cdk/testing/TestElement) for the given raw element.                                                                                                                                                                                     |
| `abstract createEnvironment(element: E): HarnessEnvironment` | Creates a [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) rooted at the given raw element.                                                                                                                                                                 |
| `abstract getAllRawElements(selector: string): Promise<E[]>` | Gets all of the raw elements under the root element of the environment matching the given selector.                                                                                                                                                                    |
| `abstract forceStabilize(): Promise<void>`                   | Gets a `Promise` that resolves when the [`NgZone`](/api/core/NgZone) is stable. Additionally, if applicable, tells [`NgZone`](/api/core/NgZone) to stabilize (e.g. calling [`flush()`](/api/core/testing/flush) in a [`fakeAsync`](/api/core/testing/fakeAsync) test). |
| `abstract waitForTasksOutsideAngular(): Promise<void>`       | Gets a `Promise` that resolves when the parent zone of [`NgZone`](/api/core/NgZone) is stable.                                                                                                                                                                         |

In addition to implementing the missing methods, this class should provide a way for test authors to get [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances. You should define a protected constructor and provide a static method called `loader` that returns a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance. This allows test authors to write code like: `SomeHarnessEnvironment.loader().getHarness(...)`. Depending on the needs of the particular environment, the class may provide several different static methods or require arguments to be passed. (e.g. the `loader` method on [`TestbedHarnessEnvironment`](/api/cdk/testing/testbed/TestbedHarnessEnvironment) takes a [`ComponentFixture`](/api/core/testing/ComponentFixture), and the class provides additional static methods called `documentRootLoader` and `harnessForFixture`).

The [`TestbedHarnessEnvironment`](/api/cdk/testing/testbed/TestbedHarnessEnvironment) and [SeleniumWebDriverHarnessEnvironment](/api/cdk/testing/selenium-webdriver/SeleniumWebDriverHarnessEnvironment) implementations in Angular CDK serve as good examples of implementations of this interface.

## [Handling auto change detection](#handling-auto-change-detection)

In order to support the [`manualChangeDetection`](/api/cdk/testing/manualChangeDetection) and parallel APIs, your environment should install a handler for the auto change detection status.

When your environment wants to start handling the auto change detection status it can call `handleAutoChangeDetectionStatus(handler)`. The handler function will receive a [`AutoChangeDetectionStatus`](/api/cdk/testing/AutoChangeDetectionStatus) which has two properties `isDisabled` and `onDetectChangesNow()`. See the [AutoChangeDetectionStatus API reference page](/api/cdk/testing/AutoChangeDetectionStatus) for more information. If your environment wants to stop handling auto change detection status it can call [`stopHandlingAutoChangeDetectionStatus()`](/api/cdk/testing/stopHandlingAutoChangeDetectionStatus).

----
url: https://angular.dev/api/common/http/withNoXsrfProtection
----

```
function withNoXsrfProtection(): HttpFeature<HttpFeatureKind.NoXsrfProtection>;
```

@returns`HttpFeature<HttpFeatureKind.NoXsrfProtection>`

## [Description](#description)

Disables XSRF protection in the configuration of the current [`HttpClient`](/api/common/http/HttpClient) instance.

This feature is incompatible with the [`withXsrfConfiguration`](/api/common/http/withXsrfConfiguration) feature.

Jump to details

----
url: https://angular.dev/api/core/injectAsync
----

```
function injectAsync<T>(
  loader: () => Promise<ProviderToken<T>>,
  options?: InjectAsyncOptions | undefined,
): () => Promise<T>;
function injectAsync<T>(
  loader: () => Promise<DefaultExport<ProviderToken<T>>>,
  options?: InjectAsyncOptions | undefined,
): () => Promise<T>;
```

A helper function that allows to inject dependencies asynchronously, which can be useful in cases when the dependency is not needed immediately and can be loaded lazily.

**NOTE:** To enable lazy loading, the injected service must be auto-provided. This means it should be decorated with either `@Injectable({providedIn: 'root'})` or [`@Service()`](/api/core/Service).

@paramloader`() => Promise<ProviderToken<T>>`

A function that returns a promise resolving to the injectable service

@paramoptions`InjectAsyncOptions | undefined`

Configuration options for the async injection

@returns`() => Promise<T>`

A function that returns a promise resolving to the requested service instance.

Usage notes

```
class MyCmp {
 someSvc = injectAsync(() => import('..'));

 async onClick() {
   (await this.someSvc()).handleClick();
 }
}

// we can also configure prefetching:
injectAsync(.., {prefetch: onIdle})
```

@paramloader`() => Promise<DefaultExport<ProviderToken<T>>>`

@paramoptions`InjectAsyncOptions | undefined`

@returns`() => Promise<T>`

## [Description](#description)

A helper function that allows to inject dependencies asynchronously, which can be useful in cases when the dependency is not needed immediately and can be loaded lazily.

**NOTE:** To enable lazy loading, the injected service must be auto-provided. This means it should be decorated with either `@Injectable({providedIn: 'root'})` or [`@Service()`](/api/core/Service).

## [Usage Notes](#usage-notes)

```
class MyCmp {
 someSvc = injectAsync(() => import('..'));

 async onClick() {
   (await this.someSvc()).handleClick();
 }
}

// we can also configure prefetching:
injectAsync(.., {prefetch: onIdle})
```

Jump to details

----
url: https://angular.dev/guide/components/queries
----

A component can define **queries** that find child elements and read values from their injectors.

Developers most commonly use queries to retrieve references to child components, directives, DOM elements, and more.

All query functions return signals that reflect the most up-to-date results. You can read the result by calling the signal function, including in [reactive contexts](guide/signals#reactive-contexts) like [`computed`](/api/core/computed) and [`effect`](/api/core/effect).

There are two categories of query: **view queries** and **content queries.**

## [View queries](#view-queries)

View queries retrieve results from the elements in the component's *view* — the elements defined in the component's own template. You can query for a single result with the [`viewChild`](/api/core/viewChild) function.

```
@Component({
  selector: 'custom-card-header',
  /*...*/
})
export class CustomCardHeader {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard {
  header = viewChild(CustomCardHeader);
  headerText = computed(() => this.header()?.text);
}
```

In this example, the `CustomCard` component queries for a child `CustomCardHeader` and uses the result in a [`computed`](/api/core/computed).

If the query does not find a result, its value is `undefined`. This may occur if the target element is hidden by `@if`. Angular keeps the result of [`viewChild`](/api/core/viewChild) up to date as your application state changes.

You can also query for multiple results with the [`viewChildren`](/api/core/viewChildren) function.

```
@Component({
  selector: 'custom-card-action',
  /*...*/
})
export class CustomCardAction {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: `
    <custom-card-action>Save</custom-card-action>
    <custom-card-action>Cancel</custom-card-action>
  `,
})
export class CustomCard {
  actions = viewChildren(CustomCardAction);
  actionsTexts = computed(() => this.actions().map((action) => action.text));
}
```

[`viewChildren`](/api/core/viewChildren) creates a signal with an `Array` of the query results.

**Queries never pierce through component boundaries.** View queries can only retrieve results from the component's template.

## [Content queries](#content-queries)

Content queries retrieve results from the elements in the component's *content*— the elements nested inside the component in the template where it's used. You can query for a single result with the [`contentChild`](/api/core/contentChild) function.

```
@Component({
  selector: 'custom-toggle',
  /*...*/
})
export class CustomToggle {
  text: string;
}

@Component({
  selector: 'custom-expando',
  /* ... */
})
export class CustomExpando {
  toggle = contentChild(CustomToggle);
  toggleText = computed(() => this.toggle()?.text);
}

@Component({
  /* ... */
  // CustomToggle is used inside CustomExpando as content.
  template: `
    <custom-expando>
      <custom-toggle>Show</custom-toggle>
    </custom-expando>
  `,
})
export class UserProfile {}
```

If the query does not find a result, its value is `undefined`. This may occur if the target element is absent or hidden by `@if`. Angular keeps the result of [`contentChild`](/api/core/contentChild) up to date as your application state changes.

By default, [`contentChild`](/api/core/contentChild) queries traverse into descendants, while [`contentChildren`](/api/core/contentChildren) queries find only *direct* children. See [Content descendants](#content-descendants).

You can also query for multiple results with the [`contentChildren`](/api/core/contentChildren) function.

```
@Component({
  selector: 'custom-menu-item',
  /*...*/
})
export class CustomMenuItem {
  text: string;
}

@Component({
  selector: 'custom-menu',
  /*...*/
})
export class CustomMenu {
  items = contentChildren(CustomMenuItem);
  itemTexts = computed(() => this.items().map((item) => item.text));
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-menu>
      <custom-menu-item>Cheese</custom-menu-item>
      <custom-menu-item>Tomato</custom-menu-item>
    </custom-menu>
  `,
})
export class UserProfile {}
```

[`contentChildren`](/api/core/contentChildren) creates a signal with an `Array` of the query results.

**Queries never pierce through component boundaries.** Content queries can only retrieve results from the same template as the component itself.

## [Required queries](#required-queries)

If a child query ([`viewChild`](/api/core/viewChild) or [`contentChild`](/api/core/contentChild)) does not find a result, its value is `undefined`. This may occur if the target element is hidden by a control flow statement like `@if` or `@for`. Because of this, the child queries return a signal that include `undefined` in their value type.

In some cases, especially with [`viewChild`](/api/core/viewChild), you know with certainty that a specific child is always available. In other cases, you may want to strictly enforce that a specific child is present. For these cases, you can use a *required query*.

```
@Component(/* ... */)
export class CustomCard {
  header = viewChild.required(CustomCardHeader);
  body = contentChild.required(CustomCardBody);
}
```

If a required query does not find a matching result, Angular reports an error. Because this guarantees that a result is available, required queries do not automatically include `undefined` in the signal's value type.

## [Query locators](#query-locators)

This first parameter for each query decorator is its **locator**.

Most of the time, you want to use a component or directive as your locator.

You can alternatively specify a string locator corresponding to a [template reference variable](guide/templates/variables#template-reference-variables).

```
@Component({
  /*...*/
  template: `
    <button #save>Save</button>
    <button #cancel>Cancel</button>
  `,
})
export class ActionBar {
  saveButton = viewChild<ElementRef<HTMLButtonElement>>('save');
}
```

If more than one element defines the same template reference variable, the query retrieves the first matching element.

Angular does not support CSS selectors as query locators.

### [Queries and the injector tree](#queries-and-the-injector-tree)

**TIP:** See [Dependency Injection](guide/di) for background on providers and Angular's injection tree.

For more advanced cases, you can use any [`ProviderToken`](/api/core/ProviderToken) as a locator. This lets you locate elements based on component and directive providers.

```
const SUB_ITEM = new InjectionToken<string>('sub-item');

@Component({
  /*...*/
  providers: [{provide: SUB_ITEM, useValue: 'special-item'}],
})
export class SpecialItem {}

@Component(/* ... */)
export class CustomList {
  subItemType = contentChild(SUB_ITEM);
}
```

The above example uses an [`InjectionToken`](/api/core/InjectionToken) as a locator, but you can use any [`ProviderToken`](/api/core/ProviderToken) to locate specific elements.

## [Query options](#query-options)

All query functions accept an options object as a second parameter. These options control how the query finds its results.

### [Reading specific values from an element's injector](#reading-specific-values-from-an-elements-injector)

By default, the query locator indicates both the element you're searching for and the value retrieved. You can alternatively specify the `read` option to retrieve a different value from the element matched by the locator.

```
@Component(/* ... */)
export class CustomExpando {
  toggle = contentChild(ExpandoContent, {read: TemplateRef});
}
```

The above example, locates an element with the directive `ExpandoContent` and retrieves the [`TemplateRef`](/api/core/TemplateRef) associated with that element.

Developers most commonly use `read` to retrieve [`ElementRef`](/api/core/ElementRef) and [`TemplateRef`](/api/core/TemplateRef).

You can also pass [`Injector`](/api/core/Injector) to `read`.

```
@Component({
  selector: 'custom-table',
  template: `
    <third-party-table #inner>
      <ng-template>
        <ng-container [ngTemplateOutlet]="columns()" [ngTemplateOutletInjector]="innerInjector()" />
      </ng-template>
    </third-party-table>
  `,
})
export class CustomTable {
  columns = contentChild(TemplateRef);
  innerInjector = viewChild('inner', {read: Injector});
}
```

The above example retrieves the node injector of the `third-party-table` element, meaning the injector as seen from that element's position in the tree. Passing it to [`NgTemplateOutlet`](/api/common/NgTemplateOutlet) through `ngTemplateOutletInjector` lets directives in the projected template inject values that the third-party component provides.

### [Content descendants](#content-descendants)

By default, [`contentChildren`](/api/core/contentChildren) queries find only *direct* children of the component and do not traverse into descendants. [`contentChild`](/api/core/contentChild) queries do traverse into descendants by default.

```
@Component({
  selector: 'custom-expando',
  /*...*/
})
export class CustomExpando {
  toggle = contentChildren(CustomToggle); // none found
  // toggle = contentChild(CustomToggle); // found
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-expando>
      <some-other-component>
        <custom-toggle>Show</custom-toggle>
      </some-other-component>
    </custom-expando>
  `,
})
export class UserProfile {}
```

In the example above, `CustomExpando` cannot find `<custom-toggle>` with [`contentChildren`](/api/core/contentChildren) because it is not a direct child of `<custom-expando>`. By setting `descendants: true`, you configure the query to traverse all descendants in the same template. Queries, however, *never* pierce into components to traverse elements in other templates.

View queries do not have this option because they *always* traverse into descendants.

## [Decorator-based queries](#decorator-based-queries)

**TIP:** While the Angular team recommends using the signal-based query function for new projects, the original decorator-based query APIs remain fully supported.

You can alternatively declare queries by adding the corresponding decorator to a property. Decorator-based queries behave the same way as signal-based queries except as described below.

### [View queries](#decorator-view-queries)

You can query for a single result with the [`@ViewChild`](/api/core/ViewChild) decorator.

```
@Component({
  selector: 'custom-card-header',
  /*...*/
})
export class CustomCardHeader {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard implements AfterViewInit {
  @ViewChild(CustomCardHeader) header: CustomCardHeader;

  ngAfterViewInit() {
    console.log(this.header.text);
  }
}
```

In this example, the `CustomCard` component queries for a child `CustomCardHeader` and accesses the result in `ngAfterViewInit`.

Angular keeps the result of [`@ViewChild`](/api/core/ViewChild) up to date as your application state changes.

**View query results become available in the `ngAfterViewInit` lifecycle method**. Before this point, the value is `undefined`. See the [Lifecycle](guide/components/lifecycle) section for details on the component lifecycle.

You can also query for multiple results with the [`@ViewChildren`](/api/core/ViewChildren) decorator.

```
@Component({
  selector: 'custom-card-action',
  /*...*/
})
export class CustomCardAction {
  text: string;
}

@Component({
  selector: 'custom-card',
  template: `
    <custom-card-action>Save</custom-card-action>
    <custom-card-action>Cancel</custom-card-action>
  `,
})
export class CustomCard implements AfterViewInit {
  @ViewChildren(CustomCardAction) actions: QueryList<CustomCardAction>;

  ngAfterViewInit() {
    this.actions.forEach((action) => {
      console.log(action.text);
    });
  }
}
```

[`@ViewChildren`](/api/core/ViewChildren) creates a [`QueryList`](/api/core/QueryList) object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property.

### [Content queries](#decorator-content-queries)

You can query for a single result with the [`@ContentChild`](/api/core/ContentChild) decorator.

```
@Component({
  selector: 'custom-toggle',
  /*...*/
})
export class CustomToggle {
  text: string;
}

@Component({
  selector: 'custom-expando',
  /*...*/
})
export class CustomExpando implements AfterContentInit {
  @ContentChild(CustomToggle) toggle: CustomToggle;

  ngAfterContentInit() {
    console.log(this.toggle.text);
  }
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-expando>
      <custom-toggle>Show</custom-toggle>
    </custom-expando>
  `,
})
export class UserProfile {}
```

In this example, the `CustomExpando` component queries for a child `CustomToggle` and accesses the result in `ngAfterContentInit`.

Angular keeps the result of [`@ContentChild`](/api/core/ContentChild) up to date as your application state changes.

**Content query results become available in the `ngAfterContentInit` lifecycle method**. Before this point, the value is `undefined`. See the [Lifecycle](guide/components/lifecycle) section for details on the component lifecycle.

You can also query for multiple results with the [`@ContentChildren`](/api/core/ContentChildren) decorator.

```
@Component({
  selector: 'custom-menu-item',
  /*...*/
})
export class CustomMenuItem {
  text: string;
}

@Component({
  selector: 'custom-menu',
  /*...*/
})
export class CustomMenu implements AfterContentInit {
  @ContentChildren(CustomMenuItem) items: QueryList<CustomMenuItem>;

  ngAfterContentInit() {
    this.items.forEach((item) => {
      console.log(item.text);
    });
  }
}

@Component({
  selector: 'user-profile',
  template: `
    <custom-menu>
      <custom-menu-item>Cheese</custom-menu-item>
      <custom-menu-item>Tomato</custom-menu-item>
    </custom-menu>
  `,
})
export class UserProfile {}
```

[`@ContentChildren`](/api/core/ContentChildren) creates a [`QueryList`](/api/core/QueryList) object that contains the query results. You can subscribe to changes to the query results over time via the `changes` property.

### [Decorator-based query options](#decorator-based-query-options)

All query decorators accept an options object as a second parameter. These options work the same way as signal-based queries except where described below.

### [Static queries](#static-queries)

[`@ViewChild`](/api/core/ViewChild) and [`@ContentChild`](/api/core/ContentChild) decorators accept the `static` option.

```
@Component({
  selector: 'custom-card',
  template: '<custom-card-header>Visit sunny California!</custom-card-header>',
})
export class CustomCard implements OnInit {
  @ViewChild(CustomCardHeader, {static: true}) header: CustomCardHeader;

  ngOnInit() {
    console.log(this.header.text);
  }
}
```

By setting `static: true`, you guarantee to Angular that the target of this query is *always* present and is not conditionally rendered. This makes the result available earlier, in the `ngOnInit` lifecycle method.

Static query results do not update after initialization.

The `static` option is not available for [`@ViewChildren`](/api/core/ViewChildren) and [`@ContentChildren`](/api/core/ContentChildren) queries.

### [Using QueryList](#using-querylist)

[`@ViewChildren`](/api/core/ViewChildren) and [`@ContentChildren`](/api/core/ContentChildren) both provide a [`QueryList`](/api/core/QueryList) object that contains a list of results.

[`QueryList`](/api/core/QueryList) offers a number of convenience APIs for working with results in an array-like manner, such as `map`, `reduce`, and `forEach`. You can get an array of the current results by calling `toArray`.

You can subscribe to the `changes` property to do something any time the results change.

## [Common query pitfalls](#common-query-pitfalls)

When using queries, common pitfalls can make your code harder to understand and maintain.

Always maintain a single source of truth for state shared between multiple components. This avoids scenarios where repeated state in different components becomes out of sync.

Avoid directly writing state to child components. This pattern can lead to brittle code that is hard to understand and is prone to [ExpressionChangedAfterItHasBeenChecked](errors/NG0100) errors.

Never directly write state to parent or ancestor components. This pattern can lead to brittle code that is hard to understand and is prone to [ExpressionChangedAfterItHasBeenChecked](errors/NG0100) errors.

----
url: https://angular.dev/api/router/ResolveData
----

```
    
      type ResolveData = {  [key: string | symbol]: ResolveFn<unknown> | DeprecatedResolve;}
    
    
```

## [Description](#description)

Represents the resolved data associated with a particular route.

Returning a [`RedirectCommand`](/api/router/RedirectCommand) directs the router to cancel the current navigation and redirect to the location provided in the [`RedirectCommand`](/api/router/RedirectCommand). Note that there are no ordering guarantees when resolvers execute. If multiple resolvers would return a [`RedirectCommand`](/api/router/RedirectCommand), only the first one returned will be used.

Jump to details

----
url: https://angular.dev/api/router/mapToCanDeactivate
----

```
function mapToCanDeactivate<T = unknown>(
  providers: Type<CanDeactivate<T>>[],
): CanDeactivateFn<T>[];
```

@paramproviders`Type<CanDeactivate<T>>[]`

@returns`CanDeactivateFn<T>[]`

## [Description](#description)

Maps an array of injectable classes with canDeactivate functions to an array of equivalent [`CanDeactivateFn`](/api/router/CanDeactivateFn) for use in a [`Route`](/api/router/Route) definition.

Usage

```
@Injectable({providedIn: 'root'})
export class AdminGuard {
  canActivate() {
    return true;
  }
}

const route: Route = {
  path: 'admin',
  canActivate: mapToCanActivate([AdminGuard]),
};
```

Jump to details

----
url: https://angular.dev/api/core/Compiler
----

```
    
      class Compiler {}
    
    
```

Compiles the given NgModule and all of its components. All templates of the components have to be inlined.

@parammoduleType`Type<T>`

@returns`NgModuleFactory<T>`

Compiles the given NgModule and all of its components

@parammoduleType`Type<T>`

@returns`Promise<NgModuleFactory<T>>`

Clears all caches.

@returns`void`

Clears the cache for the given component/ngModule.

@paramtype`Type<any>`

@returns`void`

Returns the id for a given NgModule, if one is defined and known to the compiler.

@parammoduleType`Type<any>`

@returns`string | undefined`

## [Description](#description)

Low-level service for running the angular compiler during runtime to create [`NgModuleFactory`](/api/core/NgModuleFactory) instances, which can later be used to create and render a Component instance.

Each [`@NgModule`](/api/core/NgModule) provides an own [`Compiler`](/api/core/Compiler) to its injector, that will use the directives/pipes of the ng module for compilation of components.

Jump to details

----
url: https://angular.dev/api/platform-browser/SafeUrl
----

```
    
      interface SafeUrl extends SafeValue {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/directives
----

In-depth Guides

Directives

# Directives

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/directives/overview.md "Edit this page")

Directives add behavior to elements and components in your Angular applications.

A directive can change how an element looks, how it behaves, or how it fits into the DOM. Angular ships with several built-in directives, and you can write your own.

## [When to use a directive](#when-to-use-a-directive)

Directives are most effective when they encapsulate **reusable** behavior that you want to apply to an existing element or component.

Common examples include:

* Applying the same appearance or behavior across many elements, such as autofocus or a tooltip.
* Reading from or writing to the host element's DOM, attributes, or classes.
* Adding behavior to a component you don't own without changing its source.

If you need to render your own markup or manage a piece of UI with its own template, reach for a [component](guide/components), a specialized directive with its own template.

## [A quick example](#a-quick-example)

Suppose you want elements to highlight when the user hovers over them with a mouse, changing their background color to yellow. Rather than repeat the same event-handling logic on every element, you can package that behavior in a directive and apply it wherever you need it.

The following `appHighlight` directive sets the host element's background color when the mouse enters and clears it when the mouse leaves:

```
import {Directive, signal} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  host: {
    '(mouseenter)': 'isHovered.set(true)',
    '(mouseleave)': 'isHovered.set(false)',
    '[style.background-color]': 'isHovered() ? "yellow" : null',
  },
})
export class HighlightDirective {
  protected isHovered = signal(false);
}
```

The `host` metadata listens for mouse events to update the `isHovered` signal, and binds the host element's `background-color` style to the signal's value.

Apply the directive by adding its selector as an attribute on an element:

```
<p appHighlight>Highlight me!</p>
```

Every element that carries the `appHighlight` attribute gains the same hover behavior, with the logic defined in one place.

## [Types of directives](#types-of-directives)

Angular has three primary types of directives:

| Directive type                                                  | Details                                                                           |
| :-------------------------------------------------------------- | :-------------------------------------------------------------------------------- |
| [Components](guide/components)                                  | Define reusable UI with their own template.                                       |
| [Attribute directives](guide/directives/attribute-directives)   | Change the appearance or behavior of an element, component, or another directive. |
| [Structural directives](guide/directives/structural-directives) | Change the DOM layout by adding and removing DOM elements.                        |

## [What's next](#whats-next)

Learn more about each type of directive in the following guides.

----
url: https://angular.dev/guide/legacy-animations/transition-and-triggers
----

This guide goes into depth on special transition states such as the `*` wildcard and `void`. It also shows how these states are used for elements entering and leaving a view. This section also explores multiple animation triggers, animation callbacks, and sequence-based animation using keyframes.

## [Predefined states and wildcard matching](#predefined-states-and-wildcard-matching)

In Angular, transition states can be defined explicitly through the [`state()`](api/animations/state) function, or using the predefined `*` wildcard and `void` states.

### [Wildcard state](#wildcard-state)

An asterisk `*` or *wildcard* matches any animation state. This is useful for defining transitions that apply regardless of the HTML element's start or end state.

For example, a transition of `open => *` applies when the element's state changes from open to anything else.

The following is another code sample using the wildcard state together with the previous example using the `open` and `closed` states. Instead of defining each state-to-state transition pair, any transition to `closed` takes 1 second, and any transition to `open` takes 0.5 seconds.

This allows the addition of new states without having to include separate transitions for each one.

### open-close.ts

```
animations: [
  trigger('openClose', [
    // ...
    state(
      'open',
      style({
        height: '200px',
        opacity: 1,
        backgroundColor: 'yellow',
      }),
    ),
    state(
      'closed',
      style({
        height: '100px',
        opacity: 0.8,
        backgroundColor: 'blue',
      }),
    ),
    transition('* => closed', [animate('1s')]),
    transition('* => open', [animate('0.5s')]),
  ]),
],
```

Use a double arrow syntax to specify state-to-state transitions in both directions.

### open-close.ts

```
transition('open <=> closed', [animate('0.5s')]),
```

### [Use wildcard state with multiple transition states](#use-wildcard-state-with-multiple-transition-states)

In the two-state button example, the wildcard isn't that useful because there are only two possible states, `open` and `closed`. In general, use wildcard states when an element has multiple potential states that it can change to. If the button can change from `open` to either `closed` or something like `inProgress`, using a wildcard state could reduce the amount of coding needed.

### open-close.ts

```
animations: [
  trigger('openClose', [
    // ...
    state(
      'open',
      style({
        height: '200px',
        opacity: 1,
        backgroundColor: 'yellow',
      }),
    ),
    state(
      'closed',
      style({
        height: '100px',
        opacity: 0.8,
        backgroundColor: 'blue',
      }),
    ),
    transition('open => closed', [animate('1s')]),
    transition('closed => open', [animate('0.5s')]),
    transition('* => closed', [animate('1s')]),
    transition('* => open', [animate('0.5s')]),
    transition('open <=> closed', [animate('0.5s')]),
    transition('* => open', [animate('1s', style({opacity: '*'}))]),
    transition('* => *', [animate('1s')]),
```

The `* => *` transition applies when any change between two states takes place.

Transitions are matched in the order in which they are defined. Thus, you can apply other transitions on top of the `* => *` transition. For example, define style changes or animations that would apply just to `open => closed`, then use `* => *` as a fallback for state pairings that aren't otherwise called out.

To do this, list the more specific transitions *before* `* => *`.

### [Use wildcards with styles](#use-wildcards-with-styles)

Use the wildcard `*` with a style to tell the animation to use whatever the current style value is, and animate with that. Wildcard is a fallback value that's used if the state being animated isn't declared within the trigger.

### open-close.ts

```
transition('* => open', [animate('1s', style({opacity: '*'}))]),
```

### [Void state](#void-state)

Use the `void` state to configure transitions for an element that is entering or leaving a page. See [Animating entering and leaving a view](guide/legacy-animations/transition-and-triggers#aliases-enter-and-leave).

### [Combine wildcard and void states](#combine-wildcard-and-void-states)

Combine wildcard and void states in a transition to trigger animations that enter and leave the page:

* A transition of `* => void` applies when the element leaves a view, regardless of what state it was in before it left
* A transition of `void => *` applies when the element enters a view, regardless of what state it assumes when entering
* The wildcard state `*` matches to *any* state, including `void`

## [Animate entering and leaving a view](#animate-entering-and-leaving-a-view)

This section shows how to animate elements entering or leaving a page.

Add a new behavior:

* When you add a hero to the list of heroes, it appears to fly onto the page from the left
* When you remove a hero from the list, it appears to fly out to the right

### hero-list-enter-leave.ts

```
animations: [
  trigger('flyInOut', [
    state('in', style({transform: 'translateX(0)'})),
    transition('void => *', [style({transform: 'translateX(-100%)'}), animate(100)]),
    transition('* => void', [animate(100, style({transform: 'translateX(100%)'}))]),
  ]),
],
```

In the preceding code, you applied the `void` state when the HTML element isn't attached to a view.

## [Aliases :enter and :leave](#aliases-enter-and-leave)

`:enter` and `:leave` are aliases for the `void => *` and `* => void` transitions. These aliases are used by several animation functions.

```
transition ( ':enter', [ … ] ); // alias for void => _
transition ( ':leave', [ … ] ); // alias for _ => void
```

It's harder to target an element that is entering a view because it isn't in the DOM yet. Use the aliases `:enter` and `:leave` to target HTML elements that are inserted or removed from a view.

### [Use `*ngIf` and `*ngFor` with :enter and :leave](#use-ngif-and-ngfor-with-enter-and-leave)

The `:enter` transition runs when any `*ngIf` or `*ngFor` views are placed on the page, and `:leave` runs when those views are removed from the page.

**IMPORTANT:** Entering/leaving behaviors can sometimes be confusing. As a rule of thumb consider that any element being added to the DOM by Angular passes via the `:enter` transition. Only elements being directly removed from the DOM by Angular pass via the `:leave` transition. For example, an element's view is removed from the DOM because its parent is being removed from the DOM.

This example has a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code.

### insert-remove.html

```
@if (isShown) {
  <div @myInsertRemoveTrigger class="insert-remove-container">
    <p>The box is inserted</p>
  </div>
}
```

In the component file, the `:enter` transition sets an initial opacity of 0. It then animates it to change that opacity to 1 as the element is inserted into the view.

### insert-remove.ts

```
trigger('myInsertRemoveTrigger', [
  transition(':enter', [style({opacity: 0}), animate('100ms', style({opacity: 1}))]),
  transition(':leave', [animate('100ms', style({opacity: 0}))]),
]),
```

Note that this example doesn't need to use [`state()`](api/animations/state).

## [Transition :increment and :decrement](#transition-increment-and-decrement)

The [`transition()`](/api/animations/transition) function takes other selector values, `:increment` and `:decrement`. Use these to kick off a transition when a numeric value has increased or decreased in value.

**HELPFUL:** The following example uses [`query()`](/api/animations/query) and [`stagger()`](/api/animations/stagger) methods. For more information on these methods, see the [complex sequences](guide/legacy-animations/complex-sequences) page.

### hero-list-page.ts

```
trigger('filterAnimation', [
  transition(':enter, * => 0, * => -1', []),
  transition(':increment', [
    query(
      ':enter',
      [
        style({opacity: 0, width: 0}),
        stagger(50, [animate('300ms ease-out', style({opacity: 1, width: '*'}))]),
      ],
      {optional: true},
    ),
  ]),
  transition(':decrement', [
    query(':leave', [stagger(50, [animate('300ms ease-out', style({opacity: 0, width: 0}))])]),
  ]),
]),
```

## [Boolean values in transitions](#boolean-values-in-transitions)

If a trigger contains a Boolean value as a binding value, then this value can be matched using a [`transition()`](/api/animations/transition) expression that compares `true` and `false`, or `1` and `0`.

### open-close.html

```
<div [@openClose]="isOpen ? true : false" class="open-close-container">
</div>
```

In the code snippet above, the HTML template binds a `<div>` element to a trigger named `openClose` with a status expression of `isOpen`, and with possible values of `true` and `false`. This pattern is an alternative to the practice of creating two named states like `open` and `close`.

Inside the [`@Component`](/api/core/Component) metadata under the `animations:` property, when the state evaluates to `true`, the associated HTML element's height is a wildcard style or default. In this case, the animation uses whatever height the element already had before the animation started. When the element is `closed`, the element gets animated to a height of 0, which makes it invisible.

### open-close.ts

```
animations: [
  trigger('openClose', [
    state('true', style({height: '*'})),
    state('false', style({height: '0px'})),
    transition('false <=> true', animate(500)),
  ]),
],
```

## [Multiple animation triggers](#multiple-animation-triggers)

You can define more than one animation trigger for a component. Attach animation triggers to different elements, and the parent-child relationships among the elements affect how and when the animations run.

### [Parent-child animations](#parent-child-animations)

Each time an animation is triggered in Angular, the parent animation always gets priority and child animations are blocked. For a child animation to run, the parent animation must query each of the elements containing child animations. It then lets the animations run using the [`animateChild()`](api/animations/animateChild) function.

#### [Disable an animation on an HTML element](#disable-an-animation-on-an-html-element)

A special animation control binding called `@.disabled` can be placed on an HTML element to turn off animations on that element, as well as any nested elements. When true, the `@.disabled` binding prevents all animations from rendering.

The following code sample shows how to use this feature.

### open-close.html

```
<div [@.disabled]="isDisabled">
  <div [@childAnimation]="isOpen ? 'open' : 'closed'" class="open-close-container">
    <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
  </div>
</div>
```

### open-close.ts

```
@Component({
  animations: [
    trigger('childAnimation', [
      // ...
    ]),
  ],
})
export class OpenCloseChild {
  isDisabled = false;
  isOpen = false;
}
```

When the `@.disabled` binding is true, the `@childAnimation` trigger doesn't kick off.

When an element within an HTML template has animations turned off using the `@.disabled` host binding, animations are turned off on all inner elements as well. You can't selectively turn off multiple animations on a single element.

A selective child animations can still be run on a disabled parent in one of the following ways:

* A parent animation can use the [`query()`](api/animations/query) function to collect inner elements located in disabled areas of the HTML template. Those elements can still animate.

- A child animation can be queried by a parent and then later animated with the [`animateChild()`](/api/animations/animateChild) function

#### [Disable all animations](#disable-all-animations)

To turn off all animations for an Angular application, place the `@.disabled` host binding on the topmost Angular component.

### app.ts

```
@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrls: ['app.css'],
  imports: [RouterLink, RouterOutlet],
  animations: [
    slideInAnimation,
  ],
})
export class AppComponent {
  @HostBinding('@.disabled')
  public animationsDisabled = false;
}
```

**HELPFUL:** Disabling animations application-wide is useful during end-to-end (E2E) testing.

## [Animation callbacks](#animation-callbacks)

The animation [`trigger()`](/api/animations/trigger) function emits *callbacks* when it starts and when it finishes. The following example features a component that contains an `openClose` trigger.

### open-close.ts

```
@Component({
  selector: 'app-open-close',
  animations: [
    trigger('openClose', [
      // ...
    ]),
  ],
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  onAnimationEvent(event: AnimationEvent) {
  }
}
```

In the HTML template, the animation event is passed back via `$event`, as `@triggerName.start` and `@triggerName.done`, where `triggerName` is the name of the trigger being used. In this example, the trigger `openClose` appears as follows.

### open-close.html

```
<div
  [@openClose]="isOpen ? 'open' : 'closed'"
  (@openClose.start)="onAnimationEvent($event)"
  (@openClose.done)="onAnimationEvent($event)"
  class="open-close-container"
>
</div>
```

A potential use for animation callbacks could be to cover for a slow API call, such as a database lookup. For example, an **InProgress** button can be set up to have its own looping animation while the backend system operation finishes.

Another animation can be called when the current animation finishes. For example, the button goes from the `inProgress` state to the `closed` state when the API call is completed.

An animation can influence an end user to *perceive* the operation as faster, even when it is not.

Callbacks can serve as a debugging tool, for example in conjunction with `console.warn()` to view the application's progress in a browser's Developer JavaScript Console. The following code snippet creates console log output for the original example, a button with the two states of `open` and `closed`.

### open-close.ts

```
export class OpenClose {
  onAnimationEvent(event: AnimationEvent) {
    // openClose is trigger name in this example
    console.warn(`Animation Trigger: ${event.triggerName}`);

    // phaseName is "start" or "done"
    console.warn(`Phase: ${event.phaseName}`);

    // in our example, totalTime is 1000 (number of milliseconds in a second)
    console.warn(`Total time: ${event.totalTime}`);

    // in our example, fromState is either "open" or "closed"
    console.warn(`From: ${event.fromState}`);

    // in our example, toState either "open" or "closed"
    console.warn(`To: ${event.toState}`);

    // the HTML element itself, the button in this case
    console.warn(`Element: ${event.element}`);
  }
}
```

## [Keyframes](#keyframes)

To create an animation with multiple steps run in sequence, use *keyframes*.

Angular's `keyframe()` function allows several style changes within a single timing segment. For example, the button, instead of fading, could change color several times over a single 2-second time span.

The code for this color change might look like this.

### status-slider.ts

```
transition('* => active', [
  animate(
    '2s',
    keyframes([
      style({backgroundColor: 'blue'}),
      style({backgroundColor: 'red'}),
      style({backgroundColor: 'orange'}),
    ]),
  ),
```

### [Offset](#offset)

Keyframes include an `offset` that defines the point in the animation where each style change occurs. Offsets are relative measures from zero to one, marking the beginning and end of the animation. They should be applied to each of the keyframe steps if used at least once.

Defining offsets for keyframes is optional. If you omit them, evenly spaced offsets are automatically assigned. For example, three keyframes without predefined offsets receive offsets of 0, 0.5, and 1. Specifying an offset of 0.8 for the middle transition in the preceding example might look like this.

The code with offsets specified would be as follows.

### status-slider.ts

```
transition('* => active', [
  animate(
    '2s',
    keyframes([
      style({backgroundColor: 'blue', offset: 0}),
      style({backgroundColor: 'red', offset: 0.8}),
      style({backgroundColor: '#754600', offset: 1.0}),
    ]),
  ),
]),
transition('* => inactive', [
  animate(
    '2s',
    keyframes([
      style({backgroundColor: '#754600', offset: 0}),
      style({backgroundColor: 'red', offset: 0.2}),
      style({backgroundColor: 'blue', offset: 1.0}),
    ]),
  ),
]),
```

You can combine keyframes with `duration`, `delay`, and `easing` within a single animation.

### [Keyframes with a pulsation](#keyframes-with-a-pulsation)

Use keyframes to create a pulse effect in your animations by defining styles at specific offsets throughout the animation.

Here's an example of using keyframes to create a pulse effect:

* The original `open` and `closed` states, with the original changes in height, color, and opacity, occurring over a timeframe of 1 second
* A keyframes sequence inserted in the middle that causes the button to appear to pulsate irregularly over the course of that same 1 second timeframe

The code snippet for this animation might look like this.

### open-close.ts

```
trigger('openClose', [
  state(
    'open',
    style({
      height: '200px',
      opacity: 1,
      backgroundColor: 'yellow',
    }),
  ),
  state(
    'close',
    style({
      height: '100px',
      opacity: 0.5,
      backgroundColor: 'green',
    }),
  ),
  // ...
  transition('* => *', [
    animate(
      '1s',
      keyframes([
        style({opacity: 0.1, offset: 0.1}),
        style({opacity: 0.6, offset: 0.2}),
        style({opacity: 1, offset: 0.5}),
        style({opacity: 0.2, offset: 0.7}),
      ]),
    ),
  ]),
]),
```

### [Animatable properties and units](#animatable-properties-and-units)

Angular animations are built on top of web animations, so you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1) page.

For properties with a numeric value, define a unit by providing the value as a string, in quotes, with the appropriate suffix:

* 50 pixels: `'50px'`

* Relative font size: `'3em'`

* Percentage: `'100%'`

You can also provide the value as a number. In such cases Angular assumes a default unit of pixels, or `px`. Expressing 50 pixels as `50` is the same as saying `'50px'`.

**HELPFUL:** The string `"50"` would instead not be considered valid).

### [Automatic property calculation with wildcards](#automatic-property-calculation-with-wildcards)

Sometimes, the value of a dimensional style property isn't known until runtime. For example, elements often have widths and heights that depend on their content or the screen size. These properties are often challenging to animate using CSS.

In these cases, you can use a special wildcard `*` property value under [`style()`](/api/animations/style). The value of that particular style property is computed at runtime and then plugged into the animation.

The following example has a trigger called `shrinkOut`, used when an HTML element leaves the page. The animation takes whatever height the element has before it leaves, and animates from that height to zero.

### hero-list-auto.ts

```
animations: [
  trigger('shrinkOut', [
    state('in', style({height: '*'})),
    transition('* => void', [style({height: '*'}), animate(250, style({height: 0}))]),
  ]),
],
```

### [Keyframes summary](#keyframes-summary)

The [`keyframes()`](/api/animations/keyframes) function in Angular allows you to specify multiple interim styles within a single transition. An optional `offset` can be used to define the point in the animation where each style change should occur.

## [More on Angular animations](#more-on-angular-animations)

You might also be interested in the following:

----
url: https://angular.dev/api/cdk/testing/AutoChangeDetectionStatus
----

```
    
      interface AutoChangeDetectionStatus {}
    
    
```

Whether auto change detection is disabled.

An optional callback, if present it indicates that change detection should be run immediately, while handling the status change. The callback should then be called as soon as change detection is done.

Jump to details

----
url: https://angular.dev/api/forms/signals/maxLength
----

```
function maxLength<
  TValue extends ValueWithLengthOrSize,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  maxLength: number | LogicFn<TValue, number | undefined, TPathKind>,
  config?: BaseValidatorConfig<TValue, TPathKind> | undefined,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

Path of the field to validate

@parammaxLength`number | LogicFn<TValue, number | undefined, TPathKind>`

The maximum length, or a LogicFn that returns the maximum length.

@paramconfig`BaseValidatorConfig<TValue, TPathKind> | undefined`

Optional, allows providing any of the following options:

* `error`: Custom validation error(s) to be used instead of the default [`maxLengthError()`](/api/forms/signals/maxLengthError) or a function that receives the [`FieldContext`](/api/forms/signals/FieldContext) and returns custom validation error(s).

@returns`void`

Jump to details

----
url: https://angular.dev/reference/migrations/signal-queries
----

Angular introduced improved APIs for queries that are considered production ready as of v19. Read more about signal queries and their benefits in the [dedicated guide](guide/components/queries).

To support existing teams that would like to use signal queries, the Angular team provides an automated migration that converts existing decorator query fields to the new API.

Run the schematic using the following command:

```
ng generate @angular/core:signal-queries-migration
```

Alternatively, the migration is available as a [code refactor action](https://code.visualstudio.com/docs/typescript/typescript-refactoring#_refactoring) in VSCode. Install the latest version of the VSCode extension and click onto e.g. a [`@ViewChild`](/api/core/ViewChild) field. See more details in the section [below](#vscode-extension).

## [What does the migration change?](#what-does-the-migration-change)

1. [`@ViewChild()`](/api/core/ViewChild), [`@ViewChildren`](/api/core/ViewChildren), [`@ContentChild`](/api/core/ContentChild) and [`@ContentChildren`](/api/core/ContentChildren) class members are updated to their signal equivalents.
2. References in your application to migrated queries are updated to call the signal.
   * This includes references in templates, host bindings or TypeScript code.

**Before**

```
import {Component, ContentChild, ElementRef} from '@angular/core';

@Component({
  template: `Has ref: {{ ref ? 'Yes' : 'No' }}`,
})
export class MyComponent {
  @ContentChild('someRef') ref: ElementRef | undefined = undefined;

  someMethod(): void {
    if (this.ref) {
      this.ref.nativeElement;
    }
  }
}
```

**After**

```
import {Component, contentChild, ElementRef} from '@angular/core';

@Component({
  template: `Has ref: {{ ref() ? 'Yes' : 'No' }}`,
})
export class MyComponent {
  readonly ref = contentChild<ElementRef>('someRef');

  someMethod(): void {
    const ref = this.ref();
    if (ref) {
      ref.nativeElement;
    }
  }
}
```

## [Configuration options](#configuration-options)

The migration supports a few options for fine tuning the migration to your specific needs.

### [`--path`](#--path)

By default, the migration will update your whole Angular CLI workspace. You can limit the migration to a specific sub-directory using this option.

### [`--best-effort-mode`](#--best-effort-mode)

By default, the migration skips queries that cannot be safely migrated. The migration tries to refactor code as safely as possible.

When the `--best-effort-mode` flag is enabled, the migration eagerly tries to migrate as much as possible, even if it could break your build.

### [`--insert-todos`](#--insert-todos)

When enabled, the migration will add TODOs to queries that couldn't be migrated. The TODOs will include reasoning on why queries were skipped. E.g.

```
// TODO: Skipped for migration because:
//  Your application code writes to the query. This prevents migration.
@ViewChild('ref') ref?: ElementRef;
```

### [`--analysis-dir`](#--analysis-dir)

In large projects you may use this option to reduce the amount of files being analyzed. By default, the migration analyzes the whole workspace, regardless of the `--path` option, in order to update all references affected by a query declaration being migrated.

With this option, you can limit analysis to a sub-folder. Note that this means that any references outside this directory are silently skipped, potentially breaking your build.

## [VSCode extension](#vscode-extension)

The migration is available as a [code refactor action](https://code.visualstudio.com/docs/typescript/typescript-refactoring#_refactoring) in VSCode.

To make use of the migration via VSCode, install the latest version of the VSCode extension and either click:

* on a [`@ViewChild`](/api/core/ViewChild), [`@ViewChildren`](/api/core/ViewChildren), [`@ContentChild`](/api/core/ContentChild), or [`@ContentChildren`](/api/core/ContentChildren) field.
* on a directive/component

Then, wait for the yellow lightbulb VSCode refactoring button to appear. Via this button you can then select the signal queries migration.

----
url: https://angular.dev/api/router/DebugTracingFeature
----

```
    
      type DebugTracingFeature = RouterFeature<RouterFeatureKind.DebugTracingFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ValueEqualityFn
----

## [API](/api/core/ValueEqualityFn#api)

```
    
      type ValueEqualityFn<T> = (a: T, b: T) => boolean
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/FieldStateByMode
----

```
    
      type FieldStateByMode<TValue, TKey extends string | number, TMode extends 'writable' | 'readonly'> = TMode extends 'writable' ? FieldState<TValue, TKey> : ReadonlyFieldState<TValue, TKey>
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG0209
----

The Angular runtime will throw this error when it injects a token intended to be used with `multi: true` but a non-Array was found instead. For example, [`ENVIRONMENT_INITIALIZER`](/api/core/ENVIRONMENT_INITIALIZER) should be provided like `{provide: ENVIRONMENT_INITIALIZER, multi: true, useValue: () => {...}}`.

----
url: https://angular.dev/api/forms/FormArrayDirective
----

```
    
      class FormArrayDirective extends AbstractFormDirective {}
    
    
```

Tracks the [`FormArray`](/api/forms/FormArray) bound to this directive.

Emits an event when the form submission has been triggered.

Returns the [`FormArray`](/api/forms/FormArray) bound to this directive.

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Binds an existing [`FormArray`](/api/forms/FormArray) to a DOM element.

This directive accepts an existing [`FormArray`](/api/forms/FormArray) instance. It will then use this [`FormArray`](/api/forms/FormArray) instance to match any child [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup)/[`FormRecord`](/api/forms/FormRecord), and [`FormArray`](/api/forms/FormArray) instances to child [`FormControlName`](/api/forms/FormControlName), [`FormGroupName`](/api/forms/FormGroupName), and [`FormArrayName`](/api/forms/FormArrayName) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

### [Register Form Array](#register-form-array)

The following example registers a [`FormArray`](/api/forms/FormArray) with first name and last name controls, and listens for the *ngSubmit* event when the button is clicked.

Jump to details

----
url: https://angular.dev/guide/templates/error-boundaries
----

**IMPORTANT:** `@boundary` is in [developer preview](reference/releases#developer-preview).

Angular templates support error boundaries to gracefully handle runtime errors that occur during rendering and change detection.

Error boundaries prevent a single component's failure from crashing the entire application and provide a way to display fallback UI to the user.

## [Catching errors with `@boundary` and `@error`](#catching-errors-with-boundary-and-error)

The `@boundary` block wraps a section of your template. If any component or directive inside this boundary throws an error during initialization or change detection, the framework catches the error and renders the `@error` block instead.

```
@boundary {
  <app-risky-component />
} @error {
  <p>Something went wrong!</p>
}
```

## [Accessing the error object](#accessing-the-error-object)

You can access the caught error by accessing the implicit `$error` variable:

```
@boundary {
  <app-risky-component />
} @error {
  <p>Error occurred: {{ $error.message }}</p>
}
```

## [Resetting the boundary](#resetting-the-boundary)

You can attempt to re-render the content of the `@boundary` by calling the implicit `$reset` function in the `@error` block. When called, it resets the boundary state and tries to render the original content again.

```
@boundary {
  <app-flaky-component />
} @error {
  <p>Loading failed.</p>
  <button (click)="$reset()">Try again</button>
}
```

## [Conditional error handling with `when`](#conditional-error-handling-with-when)

You can use `when` clauses to conditionally handle specific types of errors, allowing you to provide different fallback UIs. Angular evaluates this condition when it catches an error.

```
@boundary {
  <app-chart-dashboard />
} @error (let err; reset = $reset; when isRenderError(err)) {
  <p>Network issue. Check your connection.</p>
  <button (click)="reset()">Retry</button>
} @error {
  <p>An unexpected error occurred: {{ $error.message }}</p>
}
```

Order your `@error` blocks from most specific to least specific, as Angular evaluates the `when` clauses in order and uses the first one that evaluates to true. A final `@error` block without a `when` clause acts as a catch-all fallback.

## [Global error handler integration](#global-error-handler-integration)

When a boundary catches an error, Angular can still notify the global [`ErrorHandler`](/api/core/ErrorHandler). You can implement the optional `onViewError` hook in your custom [`ErrorHandler`](/api/core/ErrorHandler) to log these caught errors to your error tracking service.

```
@Injectable()
export class MyErrorHandler implements ErrorHandler {
  handleError(error: any): void {
    // Handle uncaught errors
  }

  onViewError(error: Error, details: ErrorDetails): void {
    // Handle errors caught by a @boundary
    console.warn('Caught by boundary:', details.boundary);
    myErrorTrackingService.log(error);
  }
}
```

**IMPORTANT:** If an `@error` block itself throws an error, the error propagates to the next outer `@boundary` or Angular treats it as an unhandled application error.

## [Content projection](#content-projection)

If your component uses [content projection](guide/components/content-projection), wrapping `<ng-content>` in a `@boundary` does not catch errors from projected content. That content belongs to the view that declares it, not the receiving component's view.

For example, a wrapper component with the following template does not catch errors from components projected into it:

Avoid

```
@boundary {
  <ng-content />
} @error {
  <p>Something went wrong!</p>
}
```

To catch those errors, wrap the wrapper component and its projected content in a `@boundary` in the parent template:

Prefer

```
@boundary {
  <app-wrapper>
    <app-risky-component />
  </app-wrapper>
} @error {
  <p>Something went wrong!</p>
}
```

## [Dynamic views and programmatic error handling](#dynamic-views-and-programmatic-error-handling)

Error handling isn't limited to template syntax. If you are creating components or embedded views dynamically, you can use the `onError` option to handle errors. See the [Handling rendering errors](guide/components/programmatic-rendering#handling-rendering-errors) section in the programmatic rendering guide for more information.

----
url: https://angular.dev/api/core/AfterContentChecked
----

```
    
      interface AfterContentChecked {}
    
    
```

A callback method that is invoked immediately after the default change detector has completed checking all of the directive's content.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define its own after-check functionality.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements AfterContentChecked {
        ngAfterContentChecked() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/api/core/CompilerFactory
----

```
    
      abstract class CompilerFactory {}
    
    
```

@paramoptions`CompilerOptions[] | undefined`

@returns`Compiler`

Jump to details

----
url: https://angular.dev/api/forms/FormSubmittedEvent
----

## [API](/api/forms/FormSubmittedEvent#api)

```
    
      class FormSubmittedEvent extends ControlEvent {}
    
    
```

@paramsource`AbstractControl<any, any, any>`

@returns`FormSubmittedEvent`

Jump to details

----
url: https://angular.dev/api/forms/signals/ParseResult
----

```
    
      interface ParseResult<TValue> {}
    
    
```

The parsed value. If omitted, the model is not updated.

Errors encountered during parsing, if any.

Jump to details

----
url: https://angular.dev/guide/testing/code-coverage
----

Code coverage reports show you any parts of your code base that might not be properly tested by your unit tests.

## [Prerequisites](#prerequisites)

To generate code coverage reports with Vitest, you must install the `@vitest/coverage-v8` package:

### npm

```
npm install --save-dev @vitest/coverage-v8
```

### yarn

```
yarn add --dev @vitest/coverage-v8
```

### pnpm

```
pnpm add -D @vitest/coverage-v8
```

### bun

```
bun add --dev @vitest/coverage-v8
```

## [Generating a report](#generating-a-report)

To generate a coverage report, add the `--coverage` flag to the `ng test` command:

```
ng test --coverage
```

After the tests run, the command creates a new `coverage/` directory in the project. Open the `index.html` file to see a report with your source code and code coverage values.

If you want to create code-coverage reports every time you test, you can set the `coverage` option to `true` in your `angular.json` file:

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "coverage": true
          }
        }
      }
    }
  }
}
```

## [Enforcing code coverage thresholds](#enforcing-code-coverage-thresholds)

The code coverage percentages let you estimate how much of your code is tested. If your team decides on a minimum amount to be unit tested, you can enforce this minimum in your configuration.

For example, suppose you want the code base to have a minimum of 80% code coverage. To enable this, add the `coverageThresholds` option to your `angular.json` file:

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "coverage": true,
            "coverageThresholds": {
              "statements": 80,
              "branches": 80,
              "functions": 80,
              "lines": 80
            }
          }
        }
      }
    }
  }
}
```

Now, if your coverage drops below 80% when you run your tests, the command will fail.

## [Advanced configuration](#advanced-configuration)

You can configure several other coverage options in your `angular.json` file:

* `coverageInclude`: Glob patterns of files to include in the coverage report.
* `coverageExclude`: Glob patterns of files to exclude in the coverage report.
* `coverageReporters`: An array of reporters to use (e.g., `html`, `lcov`, `json`).
* `coverageWatermarks`: An object specifying `[low, high]` watermarks for the HTML reporter, which can affect the color-coding of the report.

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "coverage": true,
            "coverageReporters": ["html", "lcov"],
            "coverageWatermarks": {
              "statements": [50, 80],
              "branches": [50, 80],
              "functions": [50, 80],
              "lines": [50, 80]
            }
          }
        }
      }
    }
  }
}
```

----
url: https://angular.dev/cli/generate/service
----

```
ng generate service [name] [options]
```

```
ng generate s [name] [options]
```

Creates a new service in your project. Services are used to encapsulate reusable logic, such as data access, API calls, or utility functions. This schematic simplifies the process of generating a new service with the necessary files and boilerplate code.

## [Arguments](#arguments)

`name`

The name for the new service. This will be used to create the service's class and spec files (e.g., `my-service.service.ts` and `my-service.service.spec.ts`).

Value Type`string`

## [Options](#options)

`add-type-to-class-name`

When true, the 'type' option will be appended to the generated class name. When false, only the file name will include the type.

Value Type`boolean`Default`true`

`flat`

Creates files at the top level of the project or the given path. If set to false, a new folder with the service's name will be created to contain the files.

Value Type`boolean`Default`true`

`injectable`

When true, generates an `@Injectable` instead of `@Service`.

Value Type`boolean`Default`false`

`project`

The name of the project where the service should be added. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`skip-tests`

Skip the generation of a unit test file `spec.ts` for the service.

Value Type`boolean`Default`false`

`type`

Append a custom type to the service's filename. For example, if you set the type to `service`, the file will be named `my-service.service.ts`.

Value Type`string`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/24-create-a-pipe
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/23-pipes-format-data)[](/tutorials/learn-angular/25-next-steps)

[](/tutorials/learn-angular/23-pipes-format-data)[](/tutorials/learn-angular/25-next-steps)

You can create custom pipes in Angular to fit your data transformation needs.

**NOTE:** Learn more about [creating custom pipes in the in-depth guide](/guide/templates/pipes#creating-custom-pipes).

In this activity, you will create a custom pipe and use it in your template.

***

A pipe is a TypeScript class with a [`@Pipe`](/api/core/Pipe) decorator. Here's an example:

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'star',
})
export class StarPipe implements PipeTransform {
  transform(value: string): string {
    return `⭐️ ${value} ⭐️`;
  }
}
```

The `StarPipe` accepts a string value and returns that string with stars around it. Take note that:

* the name in the [`@Pipe`](/api/core/Pipe) decorator configuration is what will be used in the template
* the `transform` function is where you put your logic

Alright, it's your turn to give this a try — you'll create the `ReversePipe`:

1. ### [Create the `ReversePipe`](#create-the-reversepipe)

   In `reverse.pipe.ts` add the [`@Pipe`](/api/core/Pipe) decorator to the `ReversePipe` class and provide the following configuration:

   ```
   @Pipe({
     name: 'reverse'
   })
   ```

2. ### [Implement the `transform` function](#implement-the-transform-function)

   Now the `ReversePipe` class is a pipe. Update the `transform` function to add the reversing logic:

   ```
   export class ReversePipe implements PipeTransform {
     transform(value: string): string {
       let reverse = '';

       for (let i = value.length - 1; i >= 0; i--) {
         reverse += value[i];
       }

       return reverse;
     }
   }
   ```

3. ### [Use the `ReversePipe` in the template](#use-the-reversepipe-in-the-template)

   With the pipe logic implemented, the final step is to use it in the template. In `app.ts` include the pipe in the template and add it to the component imports:

   ```
   @Component({
     ...
     template: `Reverse Machine: {{ word | reverse }}`,
     imports: [ReversePipe]
   })
   ```

And with that you've done it. Congratulations on completing this activity. You now know how to use pipes and even how to implement your own custom pipes.

----
url: https://angular.dev/api/common/http/withRequestsMadeViaParent
----

```
function withRequestsMadeViaParent(): HttpFeature<HttpFeatureKind.RequestsMadeViaParent>;
```

@returns`HttpFeature<HttpFeatureKind.RequestsMadeViaParent>`

## [Description](#description)

Configures the current [`HttpClient`](/api/common/http/HttpClient) instance to make requests via the parent injector's [`HttpClient`](/api/common/http/HttpClient) instead of directly.

By default, [`provideHttpClient`](/api/common/http/provideHttpClient) configures [`HttpClient`](/api/common/http/HttpClient) in its injector to be an independent instance. For example, even if [`HttpClient`](/api/common/http/HttpClient) is configured in the parent injector with one or more interceptors, they will not intercept requests made via this instance.

With this option enabled, once the request has passed through the current injector's interceptors, it will be delegated to the parent injector's [`HttpClient`](/api/common/http/HttpClient) chain instead of dispatched directly, and interceptors in the parent configuration will be applied to the request.

If there are several [`HttpClient`](/api/common/http/HttpClient) instances in the injector hierarchy, it's possible for [`withRequestsMadeViaParent`](/api/common/http/withRequestsMadeViaParent) to be used at multiple levels, which will cause the request to "bubble up" until either reaching the root level or an [`HttpClient`](/api/common/http/HttpClient) which was not configured with this option.

This feature cannot be combined with [`withFetch`](/api/common/http/withFetch) or [`withXhr`](/api/common/http/withXhr) in the same [`provideHttpClient()`](/api/common/http/provideHttpClient) call.

Jump to details

----
url: https://angular.dev/api/aria/menu/MenuContent
----

## [API](/api/aria/menu/MenuContent#api)

```
    
      class MenuContent {}
    
    
```

## [Description](/api/aria/menu/MenuContent#description)

Defers the rendering of the menu content.

This structural directive should be applied to an `ng-template` within a `ngMenu` or `ngMenuBar` to lazily render its content only when the menu is opened.

```
<div ngMenu #myMenu="ngMenu">
  <ng-template ngMenuContent>
    <div ngMenuItem value="Lazy Item 1">Lazy Item 1</div>
    <div ngMenuItem value="Lazy Item 2">Lazy Item 2</div>
  </ng-template>
</div>
```

Jump to details

----
url: https://angular.dev/api/core/globals/DirectiveDebugMetadata
----

```
    
      type DirectiveDebugMetadata = | AngularDirectiveDebugMetadata  | AcxDirectiveDebugMetadata  | AngularComponentDebugMetadata  | AcxComponentDebugMetadata  | WizComponentDebugMetadata
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG05102
----

Angular only recognizes three string values as global event targets: `'window'`, `'document'`, and `'body'`. If you pass anything else as a string target when registering a global event listener, Angular can't resolve it and throws this error.

## [Debugging the error](#debugging-the-error)

Check for a typo in the target name used with [`@HostListener`](/api/core/HostListener) or [`Renderer2.listen()`](/api/core/Renderer2#listen). A small mistake like `'windw'` instead of `'window'` is enough to trigger this:

```
@Component({
  selector: 'app-example',
  template: '<button>Click me</button>',
})
export class Example {
  @HostListener('windw:resize') // typo — should be 'window'
  onResize() {}
}
```

The same applies when calling [`Renderer2.listen()`](/api/core/Renderer2#listen) directly with a string that Angular doesn't know about:

```
this.renderer.listen('global', 'click', handler); // 'global' is not supported
```

## [Fixing the error](#fixing-the-error)

Correct the target to one of the three supported strings, or pass an actual DOM element instead of a string:

```
@Component({
  selector: 'app-example',
  template: '<button>Click me</button>',
})
export class Example {
  @HostListener('window:resize')
  onResize() {}
}
```

```
// Use an element reference when you need to target something more specific
this.renderer.listen(this.elementRef.nativeElement, 'click', handler);
```

----
url: https://angular.dev/api/core/Output
----

```
@Output(alias?: string);
```

## [Usage Notes](#usage-notes)

You can supply an optional name to use in templates when the component is instantiated, that maps to the name of the bound property. By default, the original name of the bound property is used for output binding.

See [`Input`](/api/core/Input) decorator for an example of providing a binding name.

Jump to details

----
url: https://angular.dev/api/router/CanLoadFn
----

* * [ActivatedRoute](/api/router/ActivatedRoute)
  * [ActivatedRouteSnapshot](/api/router/ActivatedRouteSnapshot)
  * [ActivationEnd](/api/router/ActivationEnd)
  * [ActivationStart](/api/router/ActivationStart)
  * [AutoCleanupInjectorsFeature](/api/router/AutoCleanupInjectorsFeature)
  * [BaseRouteReuseStrategy](/api/router/BaseRouteReuseStrategy)
  * [CanActivate](/api/router/CanActivate)
  * [CanActivateChild](/api/router/CanActivateChild)
  * [CanActivateChildFn](/api/router/CanActivateChildFn)
  * [CanActivateFn](/api/router/CanActivateFn)
  * [CanDeactivate](/api/router/CanDeactivate)
  * [CanDeactivateFn](/api/router/CanDeactivateFn)
  * [CanLoad](/api/router/CanLoad)
  * [CanLoadFn](/api/router/CanLoadFn)
  * [CanMatch](/api/router/CanMatch)
  * [CanMatchFn](/api/router/CanMatchFn)
  * [ChildActivationEnd](/api/router/ChildActivationEnd)
  * [ChildActivationStart](/api/router/ChildActivationStart)
  * [ChildrenOutletContexts](/api/router/ChildrenOutletContexts)
  * [ComponentInputBindingFeature](/api/router/ComponentInputBindingFeature)
  * [ComponentInputBindingOptions](/api/router/ComponentInputBindingOptions)
  * [containsTree](/api/router/containsTree)
  * [convertToParamMap](/api/router/convertToParamMap)
  * [createUrlTreeFromSnapshot](/api/router/createUrlTreeFromSnapshot)
  * [Data](/api/router/Data)
  * [DebugTracingFeature](/api/router/DebugTracingFeature)
  * [DefaultExport](/api/router/DefaultExport)
  * [DefaultTitleStrategy](/api/router/DefaultTitleStrategy)
  * [defaultUrlMatcher](/api/router/defaultUrlMatcher)
  * [DefaultUrlSerializer](/api/router/DefaultUrlSerializer)
  * [DeprecatedGuard](/api/router/DeprecatedGuard)
  * [DeprecatedResolve](/api/router/DeprecatedResolve)
  * [destroyDetachedRouteHandle](/api/router/destroyDetachedRouteHandle)
  * [DetachedRouteHandle](/api/router/DetachedRouteHandle)
  * [DisabledInitialNavigationFeature](/api/router/DisabledInitialNavigationFeature)
  * [EnabledBlockingInitialNavigationFeature](/api/router/EnabledBlockingInitialNavigationFeature)
  * [Event](/api/router/Event)
  * [EventType](/api/router/EventType)
  * [ExtraOptions](/api/router/ExtraOptions)
  * [GuardResult](/api/router/GuardResult)
  * [GuardsCheckEnd](/api/router/GuardsCheckEnd)
  * [GuardsCheckStart](/api/router/GuardsCheckStart)
  * [InitialNavigation](/api/router/InitialNavigation)
  * [InitialNavigationFeature](/api/router/InitialNavigationFeature)
  * [InMemoryScrollingFeature](/api/router/InMemoryScrollingFeature)
  * [InMemoryScrollingOptions](/api/router/InMemoryScrollingOptions)
  * [isActive](/api/router/isActive)
  * [IsActiveMatchOptions](/api/router/IsActiveMatchOptions)
  * [LoadChildren](/api/router/LoadChildren)
  * [LoadChildrenCallback](/api/router/LoadChildrenCallback)
  * [mapToCanActivate](/api/router/mapToCanActivate)
  * [mapToCanActivateChild](/api/router/mapToCanActivateChild)
  * [mapToCanDeactivate](/api/router/mapToCanDeactivate)
  * [mapToCanMatch](/api/router/mapToCanMatch)
  * [mapToResolve](/api/router/mapToResolve)
  * [MaybeAsync](/api/router/MaybeAsync)
  * [Navigation](/api/router/Navigation)
  * [NavigationBehaviorOptions](/api/router/NavigationBehaviorOptions)
  * [NavigationCancel](/api/router/NavigationCancel)
  * [NavigationCancellationCode](/api/router/NavigationCancellationCode)
  * [NavigationEnd](/api/router/NavigationEnd)
  * [NavigationError](/api/router/NavigationError)
  * [NavigationErrorHandlerFeature](/api/router/NavigationErrorHandlerFeature)
  * [NavigationExtras](/api/router/NavigationExtras)
  * [NavigationSkipped](/api/router/NavigationSkipped)
  * [NavigationSkippedCode](/api/router/NavigationSkippedCode)
  * [NavigationStart](/api/router/NavigationStart)
  * [nonBlocking](/api/router/nonBlocking)
  * [NoPreloading](/api/router/NoPreloading)
  * [OnSameUrlNavigation](/api/router/OnSameUrlNavigation)
  * [OutletContext](/api/router/OutletContext)
  * [ParamMap](/api/router/ParamMap)
  * [Params](/api/router/Params)
  * [PartialMatchRouteSnapshot](/api/router/PartialMatchRouteSnapshot)
  * [PreloadAllModules](/api/router/PreloadAllModules)
  * [PreloadingFeature](/api/router/PreloadingFeature)
  * [PreloadingStrategy](/api/router/PreloadingStrategy)
  * [PRIMARY\_OUTLET](/api/router/PRIMARY_OUTLET)
  * [provideRouter](/api/router/provideRouter)
  * [QueryParamsHandling](/api/router/QueryParamsHandling)
  * [RedirectCommand](/api/router/RedirectCommand)
  * [RedirectFunction](/api/router/RedirectFunction)
  * [Resolve](/api/router/Resolve)
  * [ResolveData](/api/router/ResolveData)
  * [ResolveEnd](/api/router/ResolveEnd)
  * [ResolveFn](/api/router/ResolveFn)
  * [ResolveStart](/api/router/ResolveStart)
  * [ResourceContext](/api/router/ResourceContext)
  * [ResourceResult](/api/router/ResourceResult)
  * [Route](/api/router/Route)
  * [RouteConfigLoadEnd](/api/router/RouteConfigLoadEnd)
  * [RouteConfigLoadStart](/api/router/RouteConfigLoadStart)
  * [Router](/api/router/Router)
  * [ROUTER\_CONFIGURATION](/api/router/ROUTER_CONFIGURATION)
  * [ROUTER\_INITIALIZER](/api/router/ROUTER_INITIALIZER)
  * [ROUTER\_OUTLET\_DATA](/api/router/ROUTER_OUTLET_DATA)
  * [RouterConfigOptions](/api/router/RouterConfigOptions)
  * [RouterConfigurationFeature](/api/router/RouterConfigurationFeature)
  * [RouteReuseStrategy](/api/router/RouteReuseStrategy)
  * [RouterEvent](/api/router/RouterEvent)
  * [RouterFeature](/api/router/RouterFeature)
  * [RouterFeatures](/api/router/RouterFeatures)
  * [RouterHashLocationFeature](/api/router/RouterHashLocationFeature)
  * [RouterLink](/api/router/RouterLink)
  * [RouterLinkActive](/api/router/RouterLinkActive)
  * [RouterLinkWithHref](/api/router/RouterLinkWithHref)
  * [RouterModule](/api/router/RouterModule)
  * [RouterOutlet](/api/router/RouterOutlet)
  * [RouterOutletContract](/api/router/RouterOutletContract)
  * [RouterPreloader](/api/router/RouterPreloader)
  * [RouterResourcesFeature](/api/router/RouterResourcesFeature)
  * [RouterState](/api/router/RouterState)
  * [RouterStateSnapshot](/api/router/RouterStateSnapshot)
  * [Routes](/api/router/Routes)
  * [ROUTES](/api/router/ROUTES)
  * [RoutesRecognized](/api/router/RoutesRecognized)
  * [RunGuardsAndResolvers](/api/router/RunGuardsAndResolvers)
  * [Scroll](/api/router/Scroll)
  * [TitleStrategy](/api/router/TitleStrategy)
  * [UrlCreationOptions](/api/router/UrlCreationOptions)
  * [UrlHandlingStrategy](/api/router/UrlHandlingStrategy)
  * [UrlMatcher](/api/router/UrlMatcher)
  * [UrlMatchResult](/api/router/UrlMatchResult)
  * [UrlSegment](/api/router/UrlSegment)
  * [UrlSegmentGroup](/api/router/UrlSegmentGroup)
  * [UrlSerializer](/api/router/UrlSerializer)
  * [UrlTree](/api/router/UrlTree)
  * [VERSION](/api/router/VERSION)
  * [ViewTransitionInfo](/api/router/ViewTransitionInfo)
  * [ViewTransitionsFeature](/api/router/ViewTransitionsFeature)
  * [ViewTransitionsFeatureOptions](/api/router/ViewTransitionsFeatureOptions)
  * [withAutoCleanupInjectors](/api/router/withAutoCleanupInjectors)
  * [withComponentInputBinding](/api/router/withComponentInputBinding)
  * [withDebugTracing](/api/router/withDebugTracing)
  * [withDisabledInitialNavigation](/api/router/withDisabledInitialNavigation)
  * [withEnabledBlockingInitialNavigation](/api/router/withEnabledBlockingInitialNavigation)
  * [withExperimentalAutoCleanupInjectors](/api/router/withExperimentalAutoCleanupInjectors)
  * [withExperimentalPlatformNavigation](/api/router/withExperimentalPlatformNavigation)
  * [withHashLocation](/api/router/withHashLocation)
  * [withInMemoryScrolling](/api/router/withInMemoryScrolling)
  * [withNavigationErrorHandler](/api/router/withNavigationErrorHandler)
  * [withPreloading](/api/router/withPreloading)
  * [withRouterConfig](/api/router/withRouterConfig)
  * [withRouterResources](/api/router/withRouterResources)
  * [withViewTransitions](/api/router/withViewTransitions)

Deprecation warning

Use [`Route.canMatch`](/api/router/Route#canMatch) and [`CanMatchFn`](/api/router/CanMatchFn) instead

## [API](#api)

```
    
      type CanLoadFn = (route: Route, segments: UrlSegment[]) => MaybeAsync<GuardResult>
    
    
```

Jump to details

----
url: https://angular.dev/guide/aria/listbox
----

A directive that displays a list of options for users to select from, supporting keyboard navigation, single or multiple selection, and screen reader support.

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
```

### app.html

```
<div class="listbox-container">
  <div ngListbox [value]="['Option 1']">
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.listbox-container {
  width: 200px;
  height: 11rem;
  padding: 0.5rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
```

### app.html

```
<div class="material-listbox">
  <div ngListbox>
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
  --on-primary: var(--page-background);
}

.docs-light-mode {
  --on-primary: #fff;
}

.material-listbox {
  width: 200px;
  height: 13rem;
  padding: 0.5rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
}

[ngListbox] {
  gap: 2px;
  padding: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
}

[ngOption]:hover,
[ngOption][data-active='true'] {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

[ngOption][aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
```

### app.html

```
<div class="retro-listbox">
  <div ngListbox>
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
}

.retro-listbox {
  width: 200px;
  height: 11rem;
  padding: 0.5rem;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
}

[ngListbox] {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0 1rem;
  font-size: 0.6rem;
  min-height: 2.25rem;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:not([aria-selected='true']) .example-option-check {
  display: none;
}

.example-option-icon,
.example-option-check {
  font-size: 0.9rem;
}

.example-option-text {
  flex: 1;
}
```

## [Usage](#usage)

Listbox is a foundational directive used by the [Select](guide/aria/select), [Multiselect](guide/aria/multiselect), and [Autocomplete](guide/aria/autocomplete) patterns. For most dropdown needs, use those documented patterns instead.

Consider using listbox directly when:

* **Building custom selection components** - Creating specialized interfaces with specific behavior
* **Visible selection lists** - Displaying selectable items directly on the page (not in dropdowns)
* **Custom integration patterns** - Integrating with unique popup or layout requirements

Avoid listbox when:

* **Navigation menus are needed** - Use the [Menu](guide/aria/menu) directive for actions and commands

## [Features](#features)

Angular's listbox provides a fully accessible list implementation with:

* **Keyboard Navigation** - Navigate options with arrow keys, select with Enter or Space
* **Screen Reader Support** - Built-in ARIA attributes including role="listbox"
* **Single or Multiple Selection** - `multi` attribute controls selection mode
* **Horizontal or Vertical** - `orientation` attribute for layout direction
* **Type-ahead Search** - Type characters to jump to matching options
* **Signal-Based Reactivity** - Reactive state management using Angular signals

## [Examples](#examples)

### [Basic listbox](#basic-listbox)

Applications sometimes need selectable lists visible directly on the page rather than hidden in a dropdown. A standalone listbox provides keyboard navigation and selection for these visible list interfaces.

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  options = [
    'Option 1',
    'Option 2',
    'Option 3',
    'Option 4',
    'Option 5',
    'Option 6',
    'Option 7',
    'Option 8',
  ];
}
```

### app.html

```
<div class="listbox-container">
  <div ngListbox [value]="['Option 1']">
    @for (option of options; track option) {
      <div ngOption [value]="option">
        <span class="example-option-text">{{ option }}</span>
        <span
          class="example-option-check material-symbols-outlined"
          translate="no"
          aria-hidden="true"
          >check</span
        >
      </div>
    }
  </div>
</div>
```

The `value` model signal provides two-way binding to the selected items. With `selectionMode="explicit"`, users press Space or Enter to select options. For dropdown patterns that combine listbox with combobox and overlay positioning, see the [Select](guide/aria/select) pattern.

### [Horizontal listbox](#horizontal-listbox)

Lists sometimes work better horizontally, such as toolbar-like interfaces or tab-style selections. The `orientation` attribute changes both the layout and keyboard navigation direction.

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
```

### app.html

```
<div ngListbox aria-label="Amenities" orientation="horizontal" selectionMode="explicit" multi>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  font-size: 0.8rem;
  font-family: var(--inter-font);
}

[ngListbox] {
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
}

[ngOption] {
  cursor: pointer;
  border-radius: 1rem;
  padding: 0.3rem 1rem;
  color: var(--hot-pink);
  border: 1px solid var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:focus {
  outline: 2px solid var(--hot-pink);
  outline-offset: 2px;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--hot-pink);
}
```

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
```

### app.html

```
<div
  ngListbox
  class="material-listbox"
  aria-label="Amenities"
  orientation="horizontal"
  selectionMode="explicit"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >check</span
      >
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  font-size: 0.8rem;
  font-family: var(--inter-font);
}

[ngListbox] {
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  border-radius: 0.3rem;
  padding: 0.3rem 0.5rem;
  color: var(--hot-pink);
  border: 1px solid var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

[ngOption]:focus {
  outline: 2px solid var(--hot-pink);
  outline-offset: 2px;
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent);
}

[ngOption][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--hot-pink);
}

.check-icon {
  width: 0;
  font-size: 1.25rem;
  overflow: hidden;
  transition:
    width 0.2s ease-in-out,
    padding-right 0.2s ease-in-out;
}

[ngOption][aria-selected='true'] .check-icon {
  width: 1.5rem;
  padding-right: 0.2rem;
}
```

### app.ts

```
import {Listbox, Option} from '@angular/aria/listbox';
import {Component} from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  /** The options available in the listbox. */
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
```

### app.html

```
<div
  ngListbox
  class="retro-listbox"
  aria-label="Amenities"
  orientation="horizontal"
  selectionMode="explicit"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >check</span
      >
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background));
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow-small:
    2px 0px 0px 0px var(--tertiary-contrast), 0px 2px 0px 0px var(--tertiary-contrast),
    -2px 0px 0px 0px var(--tertiary-contrast), 0px -2px 0px 0px var(--tertiary-contrast);
}

.retro-listbox {
  gap: 0.5rem;
  display: flex;
  flex-wrap: wrap;
  padding: 0.5rem;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--septenary-contrast);
}

[ngOption] {
  display: flex;
  cursor: pointer;
  align-items: center;
  padding: 0.3rem 0.5rem;
  font-size: 0.6rem;
  box-shadow: var(--retro-flat-shadow-small);
}

[ngOption]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngOption][data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

[ngOption][aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.check-icon {
  width: 0;
  font-size: 0.9rem;
  overflow: hidden;
  transition: width 0.2s ease-in-out;
}

[ngOption][aria-selected='true'] .check-icon {
  width: 1.2rem;
}
```

With `orientation="horizontal"`, left and right arrow keys navigate between options instead of up and down. The listbox automatically handles right-to-left (RTL) languages by reversing navigation direction.

### [Selection modes](#selection-modes)

Listbox supports two selection modes that control when items become selected.

The `'follow'` mode automatically selects the focused item, providing faster interaction when selection changes frequently. The `'explicit'` mode requires Space or Enter to confirm selection, preventing accidental changes while navigating. Dropdown patterns typically use `'follow'` mode for single selection.

#### [Explicit](#explicit)

### app.ts

```
import {Component} from '@angular/core';
import {Listbox, Option} from '@angular/aria/listbox';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
```

### app.html

```
<div
  ngListbox
  aria-label="Amenities_explicit"
  orientation="horizontal"
  selectionMode="explicit"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
```

#### [Follow](#follow)

### app.ts

```
import {Component} from '@angular/core';
import {Listbox, Option} from '@angular/aria/listbox';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [Listbox, Option],
})
export class App {
  amenities = ['Washer / Dryer', 'Ramp access', 'Garden', 'Cats OK', 'Dogs OK', 'Smoke-free'];
}
```

### app.html

```
<div
  ngListbox
  aria-label="Amenities_explicit"
  orientation="horizontal"
  selectionMode="follow"
  multi
>
  @for (amenity of amenities; track amenity) {
    <div ngOption [value]="amenity" [label]="amenity">
      <span class="option-label">{{ amenity }}</span>
    </div>
  }
</div>
```

| Mode         | Description                                                                                            |
| ------------ | ------------------------------------------------------------------------------------------------------ |
| `'follow'`   | Automatically selects the focused item, providing faster interaction when selection changes frequently |
| `'explicit'` | Requires Space or Enter to confirm selection, preventing accidental changes while navigating           |

**TIP:** Dropdown patterns typically use `'follow'` mode for single selection.

## [Testing](#testing)

Angular Aria provides component harnesses for testing listbox components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ListboxHarness} from '@angular/aria/listbox/testing';
import {MyListboxComponent} from './my-listbox'; // Your component

describe('MyListboxComponent', () => {
  let fixture: ComponentFixture<MyListboxComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyListboxComponent],
    });

    fixture = TestBed.createComponent(MyListboxComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should allow selecting options', async () => {
    const listbox = await loader.getHarness(ListboxHarness);

    // Verify listbox properties
    expect(await listbox.isMulti()).toBe(true);

    // Get all options
    const options = await listbox.getOptions();
    expect(options.length).toBe(2);

    // Click an option
    await options[0].click();

    // Verify option is selected
    expect(await options[0].isSelected()).toBe(true);

    // Filter options by text
    const bananaOption = await listbox.getOptions({text: 'Banana'});
    expect(bananaOption.length).toBe(1);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Listbox`](/api/aria/listbox/Listbox)
* [`Option`](/api/aria/listbox/Option)

### [Related patterns](#related-patterns)

Listbox is used by these documented dropdown patterns:

* [Select](guide/aria/select) - Single-selection dropdown pattern using readonly combobox + listbox
* [Multiselect](guide/aria/multiselect) - Multiple-selection dropdown pattern using readonly combobox + listbox with `multi`
* [Autocomplete](guide/aria/autocomplete) - Filterable dropdown pattern using combobox + listbox

For complete dropdown patterns with trigger, popup, and overlay positioning, see those pattern guides instead of using listbox alone.

----
url: https://angular.dev/guide/testing/migrating-to-vitest
----

The Angular CLI uses [Vitest](https://vitest.dev/) as the default unit test runner for new projects. This guide provides instructions for migrating an existing project from Karma and Jasmine to Vitest.

**IMPORTANT:** Migrating an existing project to Vitest is considered experimental. This process also requires the use of the `application` build system, which is the default for all newly created projects.

## [Manual migration steps](#manual-migration-steps)

Before using the automated refactoring schematic, you must manually update your project to use the Vitest test runner.

### [1. Install dependencies](#1-install-dependencies)

Install `vitest` and a DOM emulation library. While browser testing is still possible (see [step 5](#5-configure-browser-mode-optional)), Vitest uses a DOM emulation library by default to simulate a browser environment within Node.js for faster test execution. The CLI automatically detects and uses `happy-dom` if it's installed; otherwise, it falls back to `jsdom`. You must have one of these packages installed.

### npm

```
npm install --save-dev vitest jsdom
```

### yarn

```
yarn add --dev vitest jsdom
```

### pnpm

```
pnpm add -D vitest jsdom
```

### bun

```
bun add --dev vitest jsdom
```

### [2. Update `angular.json`](#2-update-angularjson)

In your `angular.json` file, find the `test` target for your project and change the `builder` to `@angular/build:unit-test`.

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test"
        }
      }
    }
  }
}
```

The `unit-test` builder defaults to `"tsConfig": "tsconfig.spec.json"` and `"buildTarget": "::development"`. You can explicitly set these options if your project requires different values. For example, if the `development` build configuration is missing or you need different options for testing, you can create and use a `testing` or similarly named build configuration for `buildTarget`.

The `@angular/build:karma` builder previously allowed build options (like `polyfills`, `assets`, or `styles`) to be configured directly within the `test` target. The new `@angular/build:unit-test` builder does not support this. If your test-specific build options differ from your existing `development` build configuration, you must move them to a dedicated build target configuration. If your test build options already match your `development` build configuration, no action is needed.

### [3. Handle custom `karma.conf.js` configurations](#3-handle-custom-karmaconfjs-configurations)

Custom configurations in `karma.conf.js` are not automatically migrated. Before deleting your `karma.conf.js` file, review it for any custom settings that need to be migrated.

Many Karma options have equivalents in Vitest that can be set in a custom Vitest configuration file (e.g., `vitest.config.ts`) and linked to your `angular.json` via the `runnerConfig` option.

Common migration paths include:

* **Reporters**: Karma reporters must be replaced with Vitest-compatible reporters. These can often be configured directly in your `angular.json` under the `test.options.reporters` property. For more advanced configurations, use a custom `vitest.config.ts` file.
* **Plugins**: Karma plugins may have Vitest equivalents that you will need to find and install. Note that code coverage is a first-class feature in the Angular CLI and can be enabled with `ng test --coverage`.
* **Custom Browser Launchers**: These are replaced by the `browsers` option in `angular.json` and the installation of a browser provider like `@vitest/browser-playwright`.

For other settings, consult the official [Vitest documentation](https://vitest.dev/config/).

### [4. Remove Karma and `test.ts` files](#4-remove-karma-and-testts-files)

You can now delete `karma.conf.js` and `src/test.ts` from your project and uninstall the Karma-related packages. The following commands are based on the packages installed in a new Angular CLI project; your project may have other Karma-related packages to remove.

### npm

```
npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core
```

### yarn

```
yarn remove karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core
```

### pnpm

```
pnpm remove karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core
```

### bun

```
bun remove karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter jasmine-core
```

### [5. Configure browser mode (optional)](#5-configure-browser-mode-optional)

If you need to run tests in a real browser, you must install a browser provider and configure your `angular.json`.

**Install a browser provider:**

Choose one of the following browser providers based on your needs:

* **Playwright**: `@vitest/browser-playwright` for Chromium, Firefox, and WebKit.
* **WebdriverIO**: `@vitest/browser-webdriverio` for Chrome, Firefox, Safari, and Edge.
* **Preview**: `@vitest/browser-preview` for WebContainer environments (like StackBlitz).

### npm

```
npm install --save-dev @vitest/browser-playwright
```

### yarn

```
yarn add --dev @vitest/browser-playwright
```

### pnpm

```
pnpm add -D @vitest/browser-playwright
```

### bun

```
bun add --dev @vitest/browser-playwright
```

**Update `angular.json` for browser mode:**

Add the `browsers` option to your `test` target's options. The browser name depends on the provider you installed (e.g., `chromium` for Playwright, `chrome` for WebdriverIO).

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {
            "browsers": ["chromium"]
          }
        }
      }
    }
  }
}
```

Headless mode is enabled automatically if the `CI` environment variable is set or if a browser name includes "Headless" (e.g., `ChromeHeadless`). Otherwise, tests will run in a headed browser.

## [Automated test refactoring with schematics](#automated-test-refactoring-with-schematics)

**IMPORTANT:** The `refactor-jasmine-vitest` schematic is experimental and may not cover all possible test patterns. Always review the changes made by the schematic.

The Angular CLI provides the `refactor-jasmine-vitest` schematic to automatically refactor your Jasmine tests to use Vitest.

### [Overview](#overview)

The schematic automates the following transformations in your test files (`.spec.ts`):

* Converts `fit` and `fdescribe` to `it.only` and `describe.only`.
* Converts `xit` and `xdescribe` to `it.skip` and `describe.skip`.
* Converts `spyOn` calls to the equivalent `vi.spyOn`.
* Replaces `jasmine.objectContaining` with `expect.objectContaining`.
* Replaces `jasmine.any` with `expect.any`.
* Replaces `jasmine.createSpy` with `vi.fn`.
* Updates `beforeAll`, `beforeEach`, `afterAll`, and `afterEach` to their Vitest equivalents.
* Converts `fail()` to Vitest's `vi.fail()`.
* Adjusts expectations to match Vitest APIs
* Adds TODO comments for code that cannot be automatically converted

The schematic **does not** perform the following actions:

* It does not install `vitest` or other related dependencies.
* It does not change your `angular.json` to use the Vitest builder or migrate any build options (like `polyfills` or `styles`) from the `test` target.
* It does not remove `karma.conf.js` or `test.ts` files.
* It does not handle complex or nested spy scenarios, which may require manual refactoring.

### [Running the schematic](#running-the-schematic)

Once your project is configured for Vitest, you can run the schematic to refactor your test files.

To refactor **all** test files in your default project, run:

```
ng g @schematics/angular:refactor-jasmine-vitest
```

### [Options](#options)

You can use the following options to customize the schematic's behavior:

| Option                   | Description                                                                                    |
| :----------------------- | :--------------------------------------------------------------------------------------------- |
| `--project <name>`       | Specify the project to refactor in a multi-project workspace. Example: `--project=my-lib`      |
| `--include <path>`       | Refactor only a specific file or directory. Example: `--include=src/app/app.component.spec.ts` |
| `--file-suffix <suffix>` | Specify a different file suffix for test files. Example: `--file-suffix=.test.ts`              |
| `--add-imports`          | Add explicit `vitest` imports if you have disabled globals in your Vitest configuration.       |
| `--verbose`              | See detailed logging of all transformations applied.                                           |
| `--browser-mode`         | If you intend to run the tests in browser mode.                                                |

### [After migrating](#after-migrating)

After the schematic completes, it's a good practice to:

1. **Run your tests**: Execute `ng test` to ensure that all tests still pass after the refactoring.
2. **Review the changes**: Look over the changes made by the schematic, paying close attention to any complex tests, especially those with intricate spies or mocks, as they may require further manual adjustments.

The `ng test` command builds the application in *watch mode* and launches the configured runner. Watch mode is enabled by default when using an interactive terminal and not running on CI.

## [Configuration](#configuration)

The Angular CLI takes care of the Vitest configuration for you, constructing the full configuration in memory based on options in `angular.json`.

### [Custom Vitest configuration](#custom-vitest-configuration)

**IMPORTANT:** While using a custom configuration enables advanced options, the Angular team does not provide direct support for the specific contents of the configuration file or for any third-party plugins used within it. The CLI will also override certain properties (`test.projects`, `test.include`) to ensure proper operation.

You can provide a custom Vitest configuration file to override the default settings. For a full list of available options, see the official [Vitest documentation](https://vitest.dev/config/).

**1. Direct path:** Provide a direct path to a Vitest configuration file in your `angular.json`:

```
{
  "projects": {
    "your-project-name": {
      "architect": {
        "test": {
          "builder": "@angular/build:unit-test",
          "options": {"runnerConfig": "vitest.config.ts"}
        }
      }
    }
  }
}
```

**2. Automatic search for base configuration:** If you set `runnerConfig` to `true`, the builder will automatically search for a shared `vitest-base.config.*` file in your project and workspace roots.

## [`zone.js` Vitest patch](#zonejs-vitest-patch)

In order to use functions like [`fakeAsync`](/api/core/testing/fakeAsync), [`flush`](/api/core/testing/flush) or [`waitForAsync`](/api/core/testing/waitForAsync), or to enable your existing tests to work with them, you can add `zone.js/plugins/vitest-patch` to your test target polyfills in `angular.json`.

Regardless, we strongly recommend you start planning to convert your existing test suites to native `async` and Vitest fake timers, since this is the established approach.

See [an example here](/guide/testing/components-scenarios#async-test-with-a-vitest-fake-timers) for fake timers usage with Vitest.

## [Bug reports](#bug-reports)

Report issues and feature requests on [GitHub](https://github.com/angular/angular-cli/issues).

Please provide a minimal reproduction where possible to aid the team in addressing issues.

----
url: https://angular.dev/api/forms/signals/NgValidationError
----

## [API](/api/forms/signals/NgValidationError#api)

```
    
      const NgValidationError: abstract new () => NgValidationError;
    
    
```

## [Usage Notes](/api/forms/signals/NgValidationError#usage-notes)

```
const f = form(...);
for (const e of form().errors()) {
  if (e instanceof NgValidationError) {
    switch(e.kind) {
      case 'required':
        console.log('This is required!');
        break;
      case 'min':
        console.log(`Must be at least ${e.min}`);
        break;
      ...
    }
  }
}
```

Jump to details

----
url: https://angular.dev/api/router/RouterFeature
----

```
    
      interface RouterFeature<FeatureKind extends RouterFeatureKind> {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/components
----

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/components/anatomy-of-components.md "Edit this page")

**TIP:** This guide assumes you've already read the [Essentials Guide](essentials). Read that first if you're new to Angular.

Every component must have:

* A TypeScript class with *behaviors* such as handling user input and fetching data from a server
* An HTML template that controls what renders into the DOM
* A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML

You provide Angular-specific information for a component by adding a [`@Component`](/api/core/Component) [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class:

```
@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
})
export class ProfilePhoto {}
```

For full details on writing Angular templates, including data binding, event handling, and control flow, see the [Templates guide](guide/templates).

The object passed to the [`@Component`](/api/core/Component) decorator is called the component's **metadata**. This includes the `selector`, `template`, and other properties described throughout this guide.

Components can optionally include a list of CSS styles that apply to that component's DOM:

```
@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
  styles: `
    img {
      border-radius: 50%;
    }
  `,
})
export class ProfilePhoto {}
```

By default, a component's styles only affect elements defined in that component's template. See [Styling Components](guide/components/styling) for details on Angular's approach to styling.

You can alternatively choose to write your template and styles in separate files:

```
@Component({
  selector: 'profile-photo',
  templateUrl: 'profile-photo.html',
  styleUrl: 'profile-photo.css',
})
export class ProfilePhoto {}
```

This can help separate the concerns of *presentation* from *behavior* in your project. You can choose one approach for your entire project, or you can decide which to use for each component.

Both `templateUrl` and `styleUrl` are relative to the directory in which the component resides.

## [Using components](#using-components)

### [Imports in the `@Component` decorator](#imports-in-the-component-decorator)

To use a component, [directive](guide/directives), or [pipe](guide/templates/pipes), you must add it to the `imports` array in the [`@Component`](/api/core/Component) decorator:

```
import {ProfilePhoto} from './profile-photo';

@Component({
  // Import the `ProfilePhoto` component in
  // order to use it in this component's template.
  imports: [ProfilePhoto],
  /* ... */
})
export class UserProfile {}
```

By default, Angular components are *standalone*, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their [`@Component`](/api/core/Component) decorator. For these components, you instead import the [`NgModule`](/api/core/NgModule) in which the component is defined. See the full [`NgModule` guide](guide/ngmodules/overview) for details.

**IMPORTANT:** In Angular versions before 19.0.0, the `standalone` option defaults to `false`.

### [Showing components in a template](#showing-components-in-a-template)

Every component defines a [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors):

```
@Component({
  selector: 'profile-photo',
  ...
})
export class ProfilePhoto { }
```

See [Component Selectors](guide/components/selectors) for details about which types of selectors Angular supports and guidance on choosing a selector.

You show a component by creating a matching HTML element in the template of *other* components:

```
@Component({
  selector: 'profile-photo',
})
export class ProfilePhoto {}

@Component({
  imports: [ProfilePhoto],
  template: `<profile-photo />`,
})
export class UserProfile {}
```

Angular creates an instance of the component for every matching HTML element it encounters. The DOM element that matches a component's selector is referred to as that component's **host element**. The contents of a component's template are rendered inside its host element.

The DOM rendered by a component, corresponding to that component's template, is called that component's **view**.

In composing components in this way, **you can think of your Angular application as a tree of components**.

This tree structure is important to understanding several other Angular concepts, including [dependency injection](guide/di) and [child queries](guide/components/queries).

----
url: https://angular.dev/api/core/OutputOptions
----

```
    
      interface OutputOptions {}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/testing/MockPlatformLocationConfig
----

```
    
      interface MockPlatformLocationConfig {}
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/ValidationSuccess
----

```
    
      type ValidationSuccess = null | undefined | void
    
    
```

Jump to details

----
url: https://angular.dev/api/router/ActivationStart
----

```
    
      class ActivationStart {}
    
    
```

@paramsnapshot`ActivatedRouteSnapshot`

@returns`ActivationStart`

Jump to details

----
url: https://angular.dev/api/service-worker/SwUpdate
----

* * [NoNewVersionDetectedEvent](/api/service-worker/NoNewVersionDetectedEvent)
  * [provideServiceWorker](/api/service-worker/provideServiceWorker)
  * [ServiceWorkerModule](/api/service-worker/ServiceWorkerModule)
  * [SwPush](/api/service-worker/SwPush)
  * [SwRegistrationOptions](/api/service-worker/SwRegistrationOptions)
  * [SwUpdate](/api/service-worker/SwUpdate)
  * [UnrecoverableStateEvent](/api/service-worker/UnrecoverableStateEvent)
  * [VersionDetectedEvent](/api/service-worker/VersionDetectedEvent)
  * [VersionEvent](/api/service-worker/VersionEvent)
  * [VersionInstallationFailedEvent](/api/service-worker/VersionInstallationFailedEvent)
  * [VersionReadyEvent](/api/service-worker/VersionReadyEvent)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](/api/service-worker/SwUpdate#api)

```
    
      class SwUpdate {}
    
    
```

@paramsw`NgswCommChannel`

@returns`SwUpdate`

Emits a [`VersionDetectedEvent`](/api/service-worker/VersionDetectedEvent) event whenever a new version is detected on the server.

Emits a [`VersionInstallationFailedEvent`](/api/service-worker/VersionInstallationFailedEvent) event whenever checking for or downloading a new version fails.

Emits a [`VersionReadyEvent`](/api/service-worker/VersionReadyEvent) event whenever a new version has been downloaded and is ready for activation.

Emits an [`UnrecoverableStateEvent`](/api/service-worker/UnrecoverableStateEvent) event whenever the version of the app used by the service worker to serve this client is in a broken state that cannot be recovered from without a full page reload.

True if the Service Worker is enabled (supported by the browser and enabled via [`ServiceWorkerModule`](/api/service-worker/ServiceWorkerModule)).

Checks for an update and waits until the new version is downloaded from the server and ready for activation.

@returns`Promise<boolean>`

a promise that

* resolves to `true` if a new version was found and is ready to be activated.
* resolves to `false` if no new version was found
* rejects if any error occurs

Updates the current client (i.e. browser tab) to the latest version that is ready for activation.

In most cases, you should not use this method and instead should update a client by reloading the page.

Updating a client without reloading can easily result in a broken application due to a version mismatch between the application shell and other page resources, such as lazy-loaded chunks, whose filenames may change between versions.

Only use this method, if you are certain it is safe for your specific use case.

@returns`Promise<boolean>`

a promise that

* resolves to `true` if an update was activated successfully
* resolves to `false` if no update was available (for example, the client was already on the latest version).
* rejects if any error occurs

Jump to details

----
url: https://angular.dev/api/common/getLocaleCurrencyName
----

Use the `Intl` API to format a currency with from currency code

## [API](#api)

```
function getLocaleCurrencyName(locale: string): string | null;
```

@deprecated

Use the `Intl` API to format a currency with from currency code

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`string | null`

The currency name, or `null` if the main country cannot be determined.

Jump to details

----
url: https://angular.dev/api/core/NgIterable
----

```
    
      type NgIterable<T> = Array<T> | Iterable<T>
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-server/testing/ServerTestingModule
----

```
    
      class ServerTestingModule {}
    
    
```

Jump to details

----
url: https://angular.dev/cli
----

| Command                            | Alias      | Description                                                                                               |
| :--------------------------------- | :--------- | :-------------------------------------------------------------------------------------------------------- |
| [`add`](cli/add)                   |            | Adds support for an external library to your project.                                                     |
| [`analytics`](cli/analytics)       |            | Configures the gathering of Angular CLI usage metrics.                                                    |
| [`build`](cli/build)               | `b`        | Compiles an Angular application or library into an output directory named dist/ at the given output path. |
| [`cache`](cli/cache)               |            | Configure persistent disk cache and retrieve cache statistics.                                            |
| [`completion`](cli/completion)     |            | Set up Angular CLI autocompletion for your terminal.                                                      |
| [`config`](cli/config)             |            | Retrieves or sets Angular configuration values in the angular.json file for the workspace.                |
| [`deploy`](cli/deploy)             |            | Invokes the deploy builder for a specified project or for the default project in the workspace.           |
| [`e2e`](cli/e2e)                   | `e`        | Builds and serves an Angular application, then runs end-to-end tests.                                     |
| [`extract-i18n`](cli/extract-i18n) |            | Extracts i18n messages from source code.                                                                  |
| [`generate`](cli/generate)         | `g`        | Generates and/or modifies files based on a schematic.                                                     |
| [`lint`](cli/lint)                 |            | Runs linting tools on Angular application code in a given project folder.                                 |
| [`new`](cli/new)                   | `n`        | Creates a new Angular workspace.                                                                          |
| [`run`](cli/run)                   |            | Runs an Architect target with an optional custom builder configuration defined in your project.           |
| [`serve`](cli/serve)               | `s`, `dev` | Builds and serves your application, rebuilding on file changes.                                           |
| [`test`](cli/test)                 | `t`        | Runs unit tests in a project.                                                                             |
| [`update`](cli/update)             |            | Updates your workspace and its dependencies. See the [Update Guide](/update-guide).                       |
| [`version`](cli/version)           | `v`        | Outputs Angular CLI version.                                                                              |

----
url: https://angular.dev/reference/migrations/common-to-standalone
----

This migration helps projects remove imports of the [`CommonModule`](/api/common/CommonModule) inside components by adding the minimal set of directive and pipe imports each template requires (for example, [`NgIf`](/api/common/NgIf), [`NgFor`](/api/common/NgFor), [`AsyncPipe`](/api/common/AsyncPipe), etc.).

Run the schematic using the following command:

```
ng generate @angular/core:common-to-standalone
```

## [Options](/reference/migrations/common-to-standalone#options)

| Option | Details                                                                                                                       |
| :----- | :---------------------------------------------------------------------------------------------------------------------------- |
| `path` | The path (relative to project root) to migrate. Defaults to `./`. Use this to incrementally migrate a subset of your project. |

## [Example](/reference/migrations/common-to-standalone#example)

Before:

```
import {Component} from '@angular/core';
import {CommonModule} from '@angular/common';

@Component({
  selector: 'app-example',
  imports: [CommonModule],
  template: `
    <div *ngIf="show">
      {{ data | async | json }}
    </div>
  `,
})
export class Example {
  show = true;
  data = Promise.resolve({message: 'Hello'});
}
```

After running the migration (component imports added, CommonModule removed):

```
import {Component} from '@angular/core';
import {AsyncPipe, JsonPipe, NgIf} from '@angular/common';

@Component({
  selector: 'app-example',
  imports: [AsyncPipe, JsonPipe, NgIf],
  template: `
    <div *ngIf="show">
      {{ data | async | json }}
    </div>
  `,
})
export class Example {
  show = true;
  data = Promise.resolve({message: 'Hello'});
}
```

----
url: https://angular.dev/api/core/DebounceTimer
----

```
    
      type DebounceTimer<T> = | number  | ((value: T, lastValue: ResourceSnapshot<T>) => Promise<void> | void)
    
    
```

Jump to details

----
url: https://angular.dev/api/core/assertPlatform
----

```
function assertPlatform(requiredToken: any): PlatformRef;
```

@paramrequiredToken`any`

@returns`PlatformRef`

Jump to details

----
url: https://angular.dev/api/router/EnabledBlockingInitialNavigationFeature
----

```
    
      type EnabledBlockingInitialNavigationFeature = RouterFeature<RouterFeatureKind.EnabledBlockingInitialNavigationFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/testbed/UnitTestElement
----

* * [Fixture](/api/cdk/testing/testbed/Fixture)
  * [TestbedHarnessEnvironment](/api/cdk/testing/testbed/TestbedHarnessEnvironment)
  * [TestbedHarnessEnvironmentOptions](/api/cdk/testing/testbed/TestbedHarnessEnvironmentOptions)
  * [UnitTestElement](/api/cdk/testing/testbed/UnitTestElement)

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

## [API](/api/cdk/testing/testbed/UnitTestElement#api)

```
    
      class UnitTestElement implements TestElement {
}
    
    
```

@paramelement`Element`

@param\_stabilize`() => Promise<void>`

@returns`UnitTestElement`

Blur the element.

@returns`Promise<void>`

Clear the element's input (for input and textarea elements only).

@returns`Promise<void>`

Click the element at the default location for the current environment. If you need to guarantee the element is clicked at a specific location, consider using `click('center')` or `click(x, y)` instead.

@parammodifiers`any`

@returns`Promise<void>`

Click the element at the element's center.

@paramlocation`"center"`

@parammodifiers`any`

@returns`Promise<void>`

Click the element at the specified coordinates relative to the top-left of the element.

@paramrelativeX`number`

Coordinate within the element, along the X-axis at which to click.

@paramrelativeY`number`

Coordinate within the element, along the Y-axis at which to click.

@parammodifiers`any`

@parammodifiers`any`

Sends the given string to the input as a series of key presses. Also fires input events and attempts to add the string to the Element's value. Note that this cannot reproduce native browser behavior for keyboard shortcuts such as Tab, Ctrl + A, etc.

@paramkeys`any[]`

@returns`Promise<void>`

Sends the given string to the input as a series of key presses. Also fires input events and attempts to add the string to the Element's value.

@parammodifiers`ModifierKeys`

@paramkeys`any[]`

@returns`Promise<void>`

Gets the text from the element.

@paramoptions`any`

Sets the value of a property of an input.


----
url: https://angular.dev/guide/di/defining-dependency-providers
----

- - [Overview](/guide/di)
  - [Creating and using services](/guide/di/creating-and-using-services)
  - [Lazy loading services New](/guide/di/lazy-loading-services)
  - [Defining dependency providers](/guide/di/defining-dependency-providers)
  - [Injection context](/guide/di/dependency-injection-context)
  - [Hierarchical injectors](/guide/di/hierarchical-dependency-injection)
  - [Optimizing injection tokens](/guide/di/lightweight-injection-tokens)
  - [DI in action](/guide/di/di-in-action)
  - [Debugging and troubleshooting DI New](/guide/di/debugging-and-troubleshooting-di)

Angular provides two ways to make services available for injection:

1. **Automatic provision** - Using `providedIn` in the [`@Injectable`](/api/core/Injectable) decorator, the [`@Service`](guide/di/creating-and-using-services#using-the-service-vs-injectable-decorator) decorator, or by providing a factory in the [`InjectionToken`](/api/core/InjectionToken) configuration
2. **Manual provision** - Using the `providers` array in components, directives, routes, or application config

In the [previous guide](/guide/di/creating-and-using-services), you learned how to create services using `providedIn: 'root'`, which handles most common use cases. This guide explores additional patterns for both automatic and manual provider configuration.

## [Automatic provision for non-class dependencies](#automatic-provision-for-non-class-dependencies)

While the [`@Injectable`](/api/core/Injectable) decorator with `providedIn: 'root'` works great for services (classes), you might need to provide other types of values globally - like configuration objects, functions, or primitive values. Angular provides [`InjectionToken`](/api/core/InjectionToken) for this purpose.

### [What is an InjectionToken?](#what-is-an-injectiontoken)

An [`InjectionToken`](/api/core/InjectionToken) is an object that Angular's dependency injection system uses to uniquely identify values for injection. Think of it as a special key that lets you store and retrieve any type of value in Angular's DI system:

```
import {InjectionToken} from '@angular/core';

// Create a token for a string value
export const API_URL = new InjectionToken<string>('api.url');

// Create a token for a function
export const LOGGER = new InjectionToken<(msg: string) => void>('logger.function');

// Create a token for a complex type
export interface Config {
  apiUrl: string;
  timeout: number;
}
export const CONFIG_TOKEN = new InjectionToken<Config>('app.config');
```

**NOTE:** The string parameter (e.g., `'api.url'`) is a description purely for debugging — Angular identifies tokens by their object reference, not this string.

### [InjectionToken with `providedIn: 'root'`](#injectiontoken-with-providedin-root)

An [`InjectionToken`](/api/core/InjectionToken) that has a `factory` results in `providedIn: 'root'` by default (but can be overridden via the `providedIn` prop).

### /app/config.token.ts

```
import {InjectionToken} from '@angular/core';

export interface AppConfig {
  apiUrl: string;
  version: string;
  features: Record<string, boolean>;
}

// Globally available configuration using providedIn
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config', {
  providedIn: 'root',
  factory: () => ({
    apiUrl: 'https://api.example.com',
    version: '1.0.0',
    features: {
      darkMode: true,
      analytics: false,
    },
  }),
});

// No need to add to providers array - available everywhere!
@Component({
  selector: 'app-header',
  template: `<h1>Version: {{ config.version }}</h1>`,
})
export class Header {
  config = inject(APP_CONFIG); // Automatically available
}
```

### [When to use InjectionToken with factory functions](#when-to-use-injectiontoken-with-factory-functions)

InjectionToken with factory functions is ideal when you can't use a class but need to provide dependencies globally:

### /app/logger.token.ts

```
import {InjectionToken, inject} from '@angular/core';
import {APP_CONFIG} from './config.token';

// Logger function type
export type LoggerFn = (level: string, message: string) => void;

// Global logger function with dependencies
export const LOGGER_FN = new InjectionToken<LoggerFn>('logger.function', {
  providedIn: 'root',
  factory: () => {
    const config = inject(APP_CONFIG);

    return (level: string, message: string) => {
      if (config.features.logging !== false) {
        console[level](`[${new Date().toISOString()}] ${message}`);
      }
    };
  },
});
```

### /app/storage.token.ts

```
// Providing browser APIs as tokens
export const LOCAL_STORAGE = new InjectionToken<Storage>('localStorage', {
  // providedIn: 'root' is configured as the default
  factory: () => window.localStorage,
});

export const SESSION_STORAGE = new InjectionToken<Storage>('sessionStorage', {
  providedIn: 'root',
  factory: () => window.sessionStorage,
});
```

### /app/feature-flags.token.ts

```
// Complex configuration with runtime logic
export const FEATURE_FLAGS = new InjectionToken<Map<string, boolean>>('feature.flags', {
  providedIn: 'root',
  factory: () => {
    const flags = new Map<string, boolean>();

    // Parse from environment or URL params
    const urlParams = new URLSearchParams(window.location.search);
    const enableBeta = urlParams.get('beta') === 'true';

    flags.set('betaFeatures', enableBeta);
    flags.set('darkMode', true);
    flags.set('newDashboard', false);

    return flags;
  },
});
```

This approach offers several advantages:

* **No manual provider configuration needed** - Works just like `providedIn: 'root'` for services
* **Tree-shakeable** - Only included if actually used
* **Type-safe** - Full TypeScript support for non-class values
* **Can inject other dependencies** - Factory functions can use `inject()` to access other services

## [Understanding manual provider configuration](#understanding-manual-provider-configuration)

When you need more control than `providedIn: 'root'` offers, you can manually configure providers. Manual configuration through the `providers` array is useful when:

1. **The service doesn't have `providedIn`** - Services without automatic provision must be manually provided
2. **You want a new instance** - To create a separate instance at the component/directive level instead of using the shared one
3. **You need runtime configuration** - When service behavior depends on runtime values
4. **You're providing non-class values** - Configuration objects, functions, or primitive values

### [Example: Service without `providedIn`](#example-service-without-providedin)

```
import {Injectable, Component, inject} from '@angular/core';

// Service without providedIn
@Injectable()
export class LocalDataStore {
  private data: string[] = [];

  addData(item: string) {
    this.data.push(item);
  }
}

// Component must provide it
@Component({
  selector: 'app-example',
  // A provider is required here because the `LocalDataStore` service has no providedIn.
  providers: [LocalDataStore],
  template: `...`,
})
export class Example {
  dataStore = inject(LocalDataStore);
}
```

### [Example: Creating component-specific instances](#example-creating-component-specific-instances)

Services with `providedIn: 'root'` can be overridden at the component level. This ties the instance of the service to the life of a component. As a result, when the component gets destroyed, the provided service is also destroyed as well.

```
import {Injectable, Component, inject} from '@angular/core';

@Injectable({providedIn: 'root'})
export class DataStore {
  private data: ListItem[] = [];
}

// This component gets its own instance
@Component({
  selector: 'app-isolated',
  // Creates new instance of `DataStore` rather than using the root-provided instance.
  providers: [DataStore],
  template: `...`,
})
export class Isolated {
  dataStore = inject(DataStore); // Component-specific instance
}
```

## [Injector hierarchy in Angular](#injector-hierarchy-in-angular)

Angular's dependency injection system is hierarchical. When a component requests a dependency, Angular starts with that component's injector and walks up the tree until it finds a provider for that dependency. Each component in your application tree can have its own injector, and these injectors form a hierarchy that mirrors your component tree.

This hierarchy enables:

* **Scoped instances**: Different parts of your app can have different instances of the same service
* **Override behavior**: Child components can override providers from parent components
* **Memory efficiency**: Services are only instantiated where needed

In Angular, any element with a component or directive can provide values to all of its descendants.

In the example above:

1. `SocialApp` can provide values for `UserProfile` and `FriendList`
2. `FriendList` can provide values for injection to `FriendEntry`, but cannot provide values for injection in `UserProfile` because it's not part of the tree

## [Declaring a provider](#declaring-a-provider)

Think of Angular's dependency injection system as a hash map or dictionary. Each provider configuration object defines a key-value pair:

* **Key (Provider identifier)**: The unique identifier you use to request a dependency
* **Value**: What Angular should return when that token is requested

When manually providing dependencies, you typically see this shorthand syntax:

```
import {Component} from '@angular/core';
import {LocalService} from './local-service';

@Component({
  selector: 'app-example',
  providers: [LocalService], // Service without providedIn
})
export class Example {}
```

This is actually a shorthand for a more detailed provider configuration:

```
{
  // This is the shorthand version
  providers: [LocalService],

  // This is the full version
  providers: [
    { provide: LocalService, useClass: LocalService }
  ]
}
```

### [Provider configuration object](#provider-configuration-object)

Every provider configuration object has two primary parts:

1. **Provider identifier**: The unique key that Angular uses to get the dependency (set via the `provide` property)

2. **Value**: The actual dependency that you want Angular to fetch, configured with different keys based on the desired type:

   * `useClass` - Provides a JavaScript class
   * `useValue` - Provides a static value
   * `useFactory` - Provides a factory function that returns the value
   * `useExisting` - Provides an alias to an existing provider

### [Provider identifiers](#provider-identifiers)

Provider identifiers allow Angular's dependency injection (DI) system to retrieve a dependency through a unique ID. You can generate provider identifiers in two ways:

1. [Class names](#class-names)
2. [Injection tokens](#injection-tokens)

#### [Class names](#class-names)

Class names use the imported class directly as the identifier:

```
import {Component} from '@angular/core';
import {LocalService} from './local-service';

@Component({
  selector: 'app-example',
  providers: [{provide: LocalService, useClass: LocalService}],
})
export class Example {
  /* ... */
}
```

The class serves as both the identifier and the implementation, which is why Angular provides the shorthand `providers: [LocalService]`.

#### [Injection tokens](#injection-tokens)

Angular provides a built-in [`InjectionToken`](api/core/InjectionToken) class that creates a unique object reference for injectable values or when you want to provide multiple implementations of the same interface.

### /app/tokens.ts

```
import {InjectionToken} from '@angular/core';
import {DataService} from './data-service.interface';

export const DATA_SERVICE_TOKEN = new InjectionToken<DataService>('DataService');
```

**NOTE:** The string `'DataService'` is a description used purely for debugging purposes. Angular identifies the token by its object reference, not this string.

Use the token in your provider configuration:

```
import {Component, inject} from '@angular/core';
import {LocalDataService} from './local-data-service';
import {DATA_SERVICE_TOKEN} from './tokens';

@Component({
  selector: 'app-example',
  providers: [{provide: DATA_SERVICE_TOKEN, useClass: LocalDataService}],
})
export class Example {
  private dataService = inject(DATA_SERVICE_TOKEN);
}
```

#### [Can TypeScript interfaces be identifiers for injection?](#can-typescript-interfaces-be-identifiers-for-injection)

TypeScript interfaces cannot be used for injection because they don't exist at runtime:

```
// ❌ This won't work!
interface DataService {
  getData(): string[];
}

// Interfaces disappear after TypeScript compilation
@Component({
  providers: [
    {provide: DataService, useClass: LocalDataService}, // Error!
  ],
})
export class Example {
  private dataService = inject(DataService); // Error!
}

// ✅ Use InjectionToken instead
export const DATA_SERVICE_TOKEN = new InjectionToken<DataService>('DataService');

@Component({
  providers: [{provide: DATA_SERVICE_TOKEN, useClass: LocalDataService}],
})
export class Example {
  private dataService = inject(DATA_SERVICE_TOKEN); // Works!
}
```

The InjectionToken provides a runtime value that Angular's DI system can use, while still maintaining type safety through TypeScript's generic type parameter.

### [Provider value types](#provider-value-types)

#### [useClass](#useclass)

`useClass` provides a JavaScript class as a dependency. This is the default when using the shorthand syntax:

```
// Shorthand
providers: [DataService];

// Full syntax
providers: [{provide: DataService, useClass: DataService}];

// Different implementation
providers: [{provide: DataService, useClass: MockDataService}];

// Conditional implementation
providers: [
  {
    provide: StorageService,
    useClass: environment.production ? CloudStorageService : LocalStorageService,
  },
];
```

#### [Practical example: Logger substitution](#practical-example-logger-substitution)

You can substitute implementations to extend functionality:

```
import {Injectable, Component, inject} from '@angular/core';

// Base logger
@Injectable()
export class Logger {
  log(message: string) {
    console.log(message);
  }
}

// Enhanced logger with timestamp
@Injectable()
export class BetterLogger extends Logger {
  override log(message: string) {
    super.log(`[${new Date().toISOString()}] ${message}`);
  }
}

// Logger that includes user context
@Injectable()
export class EvenBetterLogger extends Logger {
  private userService = inject(UserService);

  override log(message: string) {
    const name = this.userService.user.name;
    super.log(`Message to ${name}: ${message}`);
  }
}

// In your component
@Component({
  selector: 'app-example',
  providers: [
    UserService, // EvenBetterLogger needs this
    {provide: Logger, useClass: EvenBetterLogger},
  ],
})
export class Example {
  private logger = inject(Logger); // Gets EvenBetterLogger instance
}
```

#### [useValue](#usevalue)

`useValue` provides any JavaScript data type as a static value:

```
providers: [
  {provide: API_URL_TOKEN, useValue: 'https://api.example.com'},
  {provide: MAX_RETRIES_TOKEN, useValue: 3},
  {provide: FEATURE_FLAGS_TOKEN, useValue: {darkMode: true, beta: false}},
];
```

**IMPORTANT:** TypeScript types and interfaces cannot serve as dependency values. They exist only at compile-time.

#### [Practical example: Application configuration](#practical-example-application-configuration)

A common use case for `useValue` is providing application configuration:

```
// Define configuration interface
export interface AppConfig {
  apiUrl: string;
  appTitle: string;
  features: {
    darkMode: boolean;
    analytics: boolean;
  };
}

// Create injection token
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');

// Define configuration
const appConfig: AppConfig = {
  apiUrl: 'https://api.example.com',
  appTitle: 'My Application',
  features: {
    darkMode: true,
    analytics: false,
  },
};

// Provide in bootstrap
bootstrapApplication(AppComponent, {
  providers: [{provide: APP_CONFIG, useValue: appConfig}],
});

// Use in component
@Component({
  selector: 'app-header',
  template: `<h1>{{ title }}</h1>`,
})
export class Header {
  private config = inject(APP_CONFIG);
  title = this.config.appTitle;
}
```

#### [useFactory](#usefactory)

`useFactory` provides a function that generates a new value for injection:

```
export const loggerFactory = (config: AppConfig) => {
  return new LoggerService(config.logLevel, config.endpoint);
};

providers: [
  {
    provide: LoggerService,
    useFactory: loggerFactory,
    deps: [APP_CONFIG], // Dependencies for the factory function
  },
];
```

You can mark factory dependencies as optional:

```
import {Optional} from '@angular/core';

providers: [
  {
    provide: MyService,
    useFactory: (required: RequiredService, optional?: OptionalService) => {
      return new MyService(required, optional || new DefaultService());
    },
    deps: [RequiredService, [new Optional(), OptionalService]],
  },
];
```

#### [Practical example: Configuration-based API client](#practical-example-configuration-based-api-client)

Here's a complete example showing how to use a factory to create a service with runtime configuration:

```
// Service that needs runtime configuration
class ApiClient {
  constructor(
    private http: HttpClient,
    private baseUrl: string,
    private rateLimitMs: number,
  ) {}

  async fetchData(endpoint: string) {
    // Apply rate limiting based on user tier
    await this.applyRateLimit();
    return this.http.get(`${this.baseUrl}/${endpoint}`);
  }

  private async applyRateLimit() {
    // Simplified example - real implementation would track request timing
    return new Promise((resolve) => setTimeout(resolve, this.rateLimitMs));
  }
}

// Factory function that configures based on user tier
import {inject} from '@angular/core';
import {HttpClient} from '@angular/common/http';
const apiClientFactory = () => {
  const http = inject(HttpClient);
  const userService = inject(UserService);

  // Assuming userService provides these values
  const baseUrl = userService.getApiBaseUrl();
  const rateLimitMs = userService.getRateLimit();

  return new ApiClient(http, baseUrl, rateLimitMs);
};

// Provider configuration
export const apiClientProvider = {
  provide: ApiClient,
  useFactory: apiClientFactory,
};

// Usage in component
@Component({
  selector: 'app-dashboard',
  providers: [apiClientProvider],
})
export class Dashboard {
  private apiClient = inject(ApiClient);
}
```

#### [useExisting](#useexisting)

`useExisting` creates an alias for a provider that was already defined. Both tokens return the same instance:

```
providers: [
  NewLogger, // The actual service
  {provide: OldLogger, useExisting: NewLogger}, // The alias
];
```

**IMPORTANT:** Don't confuse `useExisting` with `useClass`. `useClass` creates separate instances, while `useExisting` ensures you get the same singleton instance.

### [Multiple providers](#multiple-providers)

Use the `multi: true` flag when multiple providers contribute values to the same token:

```
export const INTERCEPTOR_TOKEN = new InjectionToken<Interceptor[]>('interceptors');

providers: [
  {provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true},
  {provide: INTERCEPTOR_TOKEN, useClass: LoggingInterceptor, multi: true},
  {provide: INTERCEPTOR_TOKEN, useClass: RetryInterceptor, multi: true},
];
```

When you inject `INTERCEPTOR_TOKEN`, you'll receive an array containing instances of all three interceptors.

## [Where can you specify providers?](#where-can-you-specify-providers)

Angular offers several levels where you can register providers, each with different implications for scope, lifecycle, and performance:

* [**Application bootstrap**](#application-bootstrap) - Global singletons available everywhere
* [**On an element (component or directive)**](#component-or-directive-providers) - Isolated instances for specific component trees
* [**Route**](#route-providers) - Feature-specific services for lazy-loaded modules

### [Application bootstrap](#application-bootstrap)

Use application-level providers in [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) when:

* **The service is used across multiple feature areas** - Services like HTTP clients, logging, or authentication that many parts of your app need
* **You want a true singleton** - One instance shared by the entire application
* **The service has no component-specific configuration** - General-purpose utilities that work the same everywhere
* **You're providing global configuration** - API endpoints, feature flags, or environment settings

### main.ts

```
bootstrapApplication(App, {
  providers: [
    {provide: API_BASE_URL, useValue: 'https://api.example.com'},
    {provide: INTERCEPTOR_TOKEN, useClass: AuthInterceptor, multi: true},
    LoggingService, // Used throughout the app
    {provide: ErrorHandler, useClass: GlobalErrorHandler},
  ],
});
```

**Benefits:**

* Single instance reduces memory usage
* Available everywhere without additional setup
* Easier to manage global state

**Drawbacks:**

* Always included in your JavaScript bundle, even if the value is never injected
* Cannot be easily customized per feature
* Harder to test individual components in isolation

#### [Why provide during bootstrap instead of using `providedIn: 'root'`?](#why-provide-during-bootstrap-instead-of-using-providedin-root)

You might want a provider during bootstrap when:

* The provider has side-effects (e.g., installing the client-side router)
* The provider requires configuration (e.g., routes)
* You're using Angular's `provideSomething` pattern (e.g., [`provideRouter`](/api/router/provideRouter), [`provideHttpClient`](/api/common/http/provideHttpClient))

### [Component or directive providers](#component-or-directive-providers)

Use component or directive providers when:

* **The service has component-specific state** - Form validators, component-specific caches, or UI state managers
* **You need isolated instances** - Each component needs its own copy of the service
* **The service is only used by one component tree** - Specialized services that don't need global access
* **You're creating reusable components** - Components that should work independently with their own services

```
// Specialized form component with its own validation service
@Component({
  selector: 'app-advanced-form',
  providers: [
    FormValidationService, // Each form gets its own validator
    {provide: FORM_CONFIG, useValue: {strictMode: true}},
  ],
})
export class AdvancedForm {}

// Modal component with isolated state management
@Component({
  selector: 'app-modal',
  providers: [
    ModalStateService, // Each modal manages its own state
  ],
})
export class Modal {}
```

**Benefits:**

* Better encapsulation and isolation
* Easier to test components individually
* Multiple instances can coexist with different configurations

**Drawbacks:**

* New instance created for each component (higher memory usage)
* No shared state between components
* Must be provided wherever needed
* Always included in the same JavaScript bundle as the component or directive, even if the value is never injected

**NOTE:** If multiple directives on the same element provide the same token, one will win, but which one is undefined.

### [Route providers](#route-providers)

Use route-level providers for:

* **Feature-specific services** - Services only needed for particular routes or feature modules
* **Lazy-loaded module dependencies** - Services that should only load with specific features
* **Route-specific configuration** - Settings that vary by application area

### routes.ts

```
export const routes: Routes = [
  {
    path: 'admin',
    providers: [
      AdminService, // Only loaded with admin routes
      {provide: FEATURE_FLAGS, useValue: {adminMode: true}},
    ],
    loadChildren: () => import('./admin/admin.routes'),
  },
  {
    path: 'shop',
    providers: [
      ShoppingCartService, // Isolated shopping state
      PaymentService,
    ],
    loadChildren: () => import('./shop/shop.routes'),
  },
];
```

Services provided at the route level are available to all components and directives within that route, as well as to its guards and resolvers.

Since these services are instantiated independently of the route’s components, they do not have direct access to route-specific information.

## [Library author patterns](#library-author-patterns)

When creating Angular libraries, you often need to provide flexible configuration options for consumers while maintaining clean APIs. Angular's own libraries demonstrate powerful patterns for achieving this.

### [The `provide` pattern](#the-provide-pattern)

Instead of requiring users to manually configure complex providers, library authors can export functions that return provider configurations:

### /libs/analytics/src/providers.ts

```
import {InjectionToken, Provider, inject} from '@angular/core';

// Configuration interface
export interface AnalyticsConfig {
  trackingId: string;
  enableDebugMode?: boolean;
  anonymizeIp?: boolean;
}

// Internal token for configuration
const ANALYTICS_CONFIG = new InjectionToken<AnalyticsConfig>('analytics.config');

// Main service that uses the configuration
export class AnalyticsService {
  private config = inject(ANALYTICS_CONFIG);

  track(event: string, properties?: any) {
    // Implementation using config
  }
}

// Provider function for consumers
export function provideAnalytics(config: AnalyticsConfig): Provider[] {
  return [{provide: ANALYTICS_CONFIG, useValue: config}, AnalyticsService];
}
```

### main.ts

```
// Usage in consumer app
bootstrapApplication(App, {
  providers: [
    provideAnalytics({
      trackingId: 'GA-12345',
      enableDebugMode: !environment.production,
    }),
  ],
});
```

### [Advanced provider patterns with options](#advanced-provider-patterns-with-options)

For more complex scenarios, you can combine multiple configuration approaches:

### /libs/http-client/src/provider.ts

```
import {Provider, InjectionToken, inject} from '@angular/core';

// Feature flags for optional functionality
export enum HttpFeatures {
  Interceptors = 'interceptors',
  Caching = 'caching',
  Retry = 'retry',
}

// Configuration interfaces
export interface HttpConfig {
  baseUrl?: string;
  timeout?: number;
  headers?: Record<string, string>;
}

export interface RetryConfig {
  maxAttempts: number;
  delayMs: number;
}

// Internal tokens
const HTTP_CONFIG = new InjectionToken<HttpConfig>('http.config');
const RETRY_CONFIG = new InjectionToken<RetryConfig>('retry.config');
const HTTP_FEATURES = new InjectionToken<Set<HttpFeatures>>('http.features');

// Core service
class HttpClientService {
  private config = inject(HTTP_CONFIG, {optional: true});
  private features = inject(HTTP_FEATURES);

  get(url: string) {
    // Use config and check features
  }
}

// Feature services
class RetryInterceptor {
  private config = inject(RETRY_CONFIG);
  // Retry logic
}

class CacheInterceptor {
  // Caching logic
}

// Main provider function
export function provideHttpClient(config?: HttpConfig, ...features: HttpFeature[]): Provider[] {
  const providers: Provider[] = [
    {provide: HTTP_CONFIG, useValue: config || {}},
    {provide: HTTP_FEATURES, useValue: new Set(features.map((f) => f.kind))},
    HttpClientService,
  ];

  // Add feature-specific providers
  features.forEach((feature) => {
    providers.push(...feature.providers);
  });

  return providers;
}

// Feature configuration functions
export interface HttpFeature {
  kind: HttpFeatures;
  providers: Provider[];
}

export function withInterceptors(...interceptors: any[]): HttpFeature {
  return {
    kind: HttpFeatures.Interceptors,
    providers: interceptors.map((interceptor) => ({
      provide: INTERCEPTOR_TOKEN,
      useClass: interceptor,
      multi: true,
    })),
  };
}

export function withCaching(): HttpFeature {
  return {
    kind: HttpFeatures.Caching,
    providers: [CacheInterceptor],
  };
}

export function withRetry(config: RetryConfig): HttpFeature {
  return {
    kind: HttpFeatures.Retry,
    providers: [{provide: RETRY_CONFIG, useValue: config}, RetryInterceptor],
  };
}

// Consumer usage with multiple features
bootstrapApplication(App, {
  providers: [
    provideHttpClient(
      {baseUrl: 'https://api.example.com'},
      withInterceptors(AuthInterceptor, LoggingInterceptor),
      withCaching(),
      withRetry({maxAttempts: 3, delayMs: 1000}),
    ),
  ],
});
```

### [Why use provider functions instead of direct configuration?](#why-use-provider-functions-instead-of-direct-configuration)

Provider functions offer several advantages for library authors:

1. **Encapsulation** - Internal tokens and implementation details remain private
2. **Type safety** - TypeScript ensures correct configuration at compile time
3. **Flexibility** - Easily compose features with `with*` pattern
4. **Future-proofing** - Internal implementation can change without breaking consumers
5. **Consistency** - Aligns with Angular's own patterns ([`provideRouter`](/api/router/provideRouter), [`provideHttpClient`](/api/common/http/provideHttpClient), etc.)

This pattern is extensively used in Angular's own libraries and is considered a best practice for library authors who need to provide configurable services.

----
url: https://angular.dev/api/core/ConstructorSansProvider
----

## [API](/api/core/ConstructorSansProvider#api)

```
    
      interface ConstructorSansProvider {}
    
    
```

A list of `token`s to be resolved by the injector.

## [Usage Notes](/api/core/ConstructorSansProvider#usage-notes)

```
@Injectable(SomeModule, {deps: []})
class MyService {}
```

Jump to details

----
url: https://angular.dev/api/core/@for
----

The `@for` block repeatedly renders content of a block for each item in a collection.

## [Syntax](#syntax)

```
@for (item of items; track item.name) {
  <li>{{ item.name }}</li>
} @empty {
  <li>There are no items.</li>
}
```

## [Description](#description)

The `@for` block renders its content in response to changes in a collection. Collections can be any JavaScript [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), but there are performance advantages of using a regular `Array`.

You can optionally include an `@empty` section immediately after the `@for` block content. The content of the `@empty` block displays when there are no items.

Angular's `@for` block does not support flow-modifying statements like JavaScript's `continue` or `break`.

### [`track` and objects identity](#track-and-objects-identity)

The value of the `track` expression determines a key used to associate array items with the views in the DOM. Having clear indication of the item identity allows Angular to execute a minimal set of DOM operations as items are added, removed or moved in a collection.

To optimize performance, especially in loops over immutable data, ensure the track expression is effectively used to identify each item uniquely. Because of the potential for poor performance, the `track` expression is required for the `@for` loops.

For collections that remain static , `track $index` provides a straightforward tracking mechanism. For dynamic collections experiencing additions, deletions, or reordering, opt for a unique property of each item as the tracking key.

Track expressions can only reference `$index`, the item, and properties and methods of the component class. If the `let` segment of the `@for` block introduced an alias for `$index`, that alias may also be referenced.

### [`$index` and other contextual variables](#index-and-other-contextual-variables)

Inside `@for` contents, several implicit variables are always available:

| Variable | Meaning                                       |
| -------- | --------------------------------------------- |
| `$count` | Number of items in a collection iterated over |
| `$index` | Index of the current row                      |
| `$first` | Whether the current row is the first row      |
| `$last`  | Whether the current row is the last row       |
| `$even`  | Whether the current row index is even         |
| `$odd`   | Whether the current row index is odd          |

These variables are always available with these names, but can be aliased via a `let` segment:

```
@for (item of items; track item.id; let idx = $index, e = $even) {
  Item #{{ idx }}: {{ item.name }}
}
```

The aliasing is especially useful in case of using nested `@for` blocks where contextual variable names could collide.

Jump to details

----
url: https://angular.dev/api/core/input
----

```
    
      
    
    
```

Initializes an input of type `T` with an initial value of `undefined`. Angular will implicitly use `undefined` as initial value.

@returns`InputSignal<T | undefined>`

Declares an input of type `T` with an explicit initial value.

@paraminitialValue`T`

@paramopts`InputOptionsWithoutTransform<T> | undefined`

@returns`InputSignal<T>`

Declares an input of type `T|undefined` without an initial value, but with input options

@paraminitialValue`undefined`

@paramopts`InputOptionsWithoutTransform<T>`

@returns`InputSignal<T | undefined>`

Declares an input of type `T` with an initial value and a transform function.

The input accepts values of type `TransformT` and the given transform function will transform the value to type `T`.

@paraminitialValue`T`

@paramopts`InputOptionsWithTransform<T, TransformT>`

@returns`InputSignalWithTransform<T, TransformT>`

Declares an input of type `T|undefined` without an initial value and with a transform function.

The input accepts values of type `TransformT` and the given transform function will transform the value to type `T|undefined`.

@paraminitialValue`undefined`

@paramopts`InputOptionsWithTransform<T | undefined, TransformT>`

@returns`InputSignalWithTransform<T | undefined, TransformT>`

Declares an input of type `T` with an initial value and a transform function that accepts values of type `unknown`.

@paraminitialValue`T`

@paramopts`InputOptionsWithTransform<T, unknown>`

@returns`InputSignalWithTransform<T, unknown>`

Declares an input of type `T|undefined` without an initial value and with a transform function that accepts values of type `unknown`.

@paraminitialValue`undefined`

@paramopts`InputOptionsWithTransform<T | undefined, unknown>`

@returns`InputSignalWithTransform<T | undefined, unknown>`

Declares a required input of type `T`.

@paramopts`InputOptionsWithoutTransform<T> | undefined`

@returns`InputSignal<T>`

Declares a required input of type `T` with a transform function.

The input accepts values of type `TransformT` and the given transform function will transform the value to type `T`.

@paramopts`InputOptionsWithTransform<T, TransformT>`

@returns`InputSignalWithTransform<T, TransformT>`

## [Usage Notes](#usage-notes)

To use signal-based inputs, import `input` from `@angular/core`.

```
import {input} from '@angular/core';
```

Inside your component, introduce a new class member and initialize it with a call to `input` or [`input.required`](/api/core/input).

```
@Component({
  ...
})
export class UserProfileComponent {
  firstName = input<string>();             // Signal<string|undefined>
  lastName  = input.required<string>();    // Signal<string>
  age       = input(0)                     // Signal<number>
}
```

Inside your component template, you can display values of the inputs by calling the signal.

```
<span>{{firstName()}}</span>
```

Jump to details

----
url: https://angular.dev/api/router/Router
----

```
    
      class Router {
}
    
    
```

An event stream for routing events.

The current state of routing in this NgModule.

True if at least one navigation event has occurred, false otherwise.

@deprecated

Configure using `providers` instead: `{provide: RouteReuseStrategy, useClass: MyStrategy}`.

A strategy for re-using routes.

@deprecated

Configure this through [`provideRouter`](/api/router/provideRouter) or [`RouterModule.forRoot`](/api/router/RouterModule#forRoot) instead.

How to handle a navigation request to the current URL.

Indicates whether the application has opted in to binding Router data to component inputs.

This option is enabled by the [`withComponentInputBinding`](/api/router/withComponentInputBinding) feature of [`provideRouter`](/api/router/provideRouter) or `bindToComponentInputs` in the [`ExtraOptions`](/api/router/ExtraOptions) of [`RouterModule.forRoot`](/api/router/RouterModule#forRoot).

Signal of the current [`Navigation`](/api/router/Navigation) object when the router is navigating, and `null` when idle.

Note: The current navigation becomes to null after the NavigationEnd event is emitted.

Sets up the location change listener and performs the initial navigation.

@returns`void`

Sets up the location change listener. This listener detects navigations triggered from outside the Router (the browser back/forward buttons, for example) and schedules a corresponding Router navigation so that the correct events, guards, etc. are triggered.

@returns`void`

The current URL.

Returns the current [`Navigation`](/api/router/Navigation) object when the router is navigating, and `null` when idle.

@deprecated

Use the `currentNavigation` signal instead.

@returns`Navigation | null`

The [`Navigation`](/api/router/Navigation) object of the most recent navigation to succeed and `null` if there has not been a successful navigation yet.

Resets the route configuration used for navigation and generating links.

@paramconfig`Routes`

The route array for the new configuration.

@returns`void`

Usage notes

```
router.resetConfig([
 { path: 'team/:id', component: TeamCmp, children: [
   { path: 'simple', component: SimpleCmp },
   { path: 'user/:name', component: UserCmp }
 ]}
]);
```

Disposes of the router.

@returns`void`

Appends URL segments to the current URL tree to create a new URL tree.

@paramcommands`readonly any[]`

An array of URL fragments with which to construct the new URL tree. If the path is static, can be the literal URL string. For a dynamic path, pass an array of path segments, followed by the parameters for each segment. The fragments are applied to the current URL tree or the one provided in the `relativeTo` property of the options object, if supplied.

@paramnavigationExtras`UrlCreationOptions`

Options that control the navigation strategy.

@returns`UrlTree`

The new URL tree.

Usage notes

```
// create /team/33/user/11
router.createUrlTree(['/team', 33, 'user', 11]);

// create /team/33;expand=true/user/11
router.createUrlTree(['/team', 33, {expand: true}, 'user', 11]);

// you can collapse static segments like this (this works only with the first passed-in value):
router.createUrlTree(['/team/33/user', userId]);

// If the first segment can contain slashes, and you do not want the router to split it,
// you can do the following:
router.createUrlTree([{segmentPath: '/one/two'}]);

// create /team/33/(user/11//right:chat)
router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: 'chat'}}]);

// remove the right secondary node
router.createUrlTree(['/team', 33, {outlets: {primary: 'user/11', right: null}}]);

// assuming the current url is `/team/33/user/11` and the route points to `user/11`

// navigate to /team/33/user/11/details
router.createUrlTree(['details'], {relativeTo: route});

// navigate to /team/33/user/22
router.createUrlTree(['../22'], {relativeTo: route});

// navigate to /team/44/user/22
router.createUrlTree(['../../team/44/user/22'], {relativeTo: route});
```

Note that a value of `null` or `undefined` for `relativeTo` indicates that the tree should be created relative to the root.

Navigates to a view using an absolute route path.

@paramurl`string | UrlTree`

An absolute path for a defined route. The function does not apply any delta to the current URL.

@paramextras`NavigationBehaviorOptions`

An object containing properties that modify the navigation strategy.

@returns`Promise<boolean>`

A Promise that resolves to 'true' when navigation succeeds, to 'false' when navigation fails, or is rejected on error.

Usage notes

The following calls request navigation to an absolute path.

```
router.navigateByUrl("/team/33/user/11");

// Navigate without updating the URL
router.navigateByUrl("/team/33/user/11", { skipLocationChange: true });
```

Navigate based on the provided array of commands and a starting point. If no starting route is provided, the navigation is absolute.

@paramcommands`readonly any[]`

An array of URL fragments with which to construct the target URL. If the path is static, can be the literal URL string. For a dynamic path, pass an array of path segments, followed by the parameters for each segment. The fragments are applied to the current URL or the one provided in the `relativeTo` property of the options object, if supplied.

@paramextras`NavigationExtras`

An options object that determines how the URL should be constructed or interpreted.

@returns`Promise<boolean>`

A Promise that resolves to `true` when navigation succeeds, or `false` when navigation fails. The Promise is rejected when an error occurs if `resolveNavigationPromiseOnError` is not `true`.

Usage notes

The following calls request navigation to a dynamic route path relative to the current URL.

```
router.navigate(['team', 33, 'user', 11], {relativeTo: route});

// Navigate without updating the URL, overriding the default behavior
router.navigate(['team', 33, 'user', 11], {relativeTo: route, skipLocationChange: true});
```

Serializes a [`UrlTree`](/api/router/UrlTree) into a string

@paramurl`UrlTree`

@returns`string`

Parses a string into a [`UrlTree`](/api/router/UrlTree)

@paramurl`string`

@returns`UrlTree`

Returns whether the url is activated.

@deprecated

Use [`IsActiveMatchOptions`](/api/router/IsActiveMatchOptions) instead.

* The equivalent [`IsActiveMatchOptions`](/api/router/IsActiveMatchOptions) for `true` is `{paths: 'exact', queryParams: 'exact', fragment: 'ignored', matrixParams: 'ignored'}`.
* The equivalent for `false` is `{paths: 'subset', queryParams: 'subset', fragment: 'ignored', matrixParams: 'ignored'}`.

@paramurl`string | UrlTree`

@paramexact`boolean`

@returns`boolean`

@deprecated

* Use the [`isActive`](/api/router/isActive) function instead.

@paramurl`string | UrlTree`

@parammatchOptions`Partial<IsActiveMatchOptions>`

@returns`boolean`

@paramurl`string | UrlTree`

@parammatchOptions`boolean | IsActiveMatchOptions`

@returns`boolean`

## [Description](#description)

A service that facilitates navigation among views and URL manipulation capabilities. This service is provided in the root scope and configured with [provideRouter](api/router/provideRouter).

## [Exported by](#exported-by)

* `RouterModule`

Jump to details

----
url: https://angular.dev/cli/generate/enum
----

```
ng generate enum [name] [options]
```

```
ng generate e [name] [options]
```

Creates a new enum in your project. Enums (enumerations) are a way to define a set of named constants, making your code more readable and maintainable. This schematic generates a new enum with the specified name and type.

## [Arguments](#arguments)

`name`

The name for the new enum. This will be used to create the enum file (e.g., `my-enum.enum.ts`).

Value Type`string`

## [Options](#options)

`project`

The name of the project where the enum should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`type`

Adds a custom type to the filename, allowing you to create more descriptive enum names. For example, if you set the type to `status`, the filename will be `my-enum.status.ts`.

Value Type`string`

Jump to details

----
url: https://angular.dev/api/core/inject
----

```
function inject<T>(token: ProviderToken<T>): T;
function inject<T>(
  token: ProviderToken<T>,
  options: InjectOptions & { optional?: false | undefined },
): T;
function inject<T>(token: ProviderToken<T>, options: InjectOptions): T | null;
function inject(token: HostAttributeToken): string;
function inject(
  token: HostAttributeToken,
  options: { optional: true },
): string | null;
function inject(
  token: HostAttributeToken,
  options: { optional: false },
): string;
```

@paramtoken`ProviderToken<T>`

A token that represents a dependency that should be injected.

@returns`T`

the injected value if operation is successful, `null` otherwise.

@paramtoken`ProviderToken<T>`

A token that represents a dependency that should be injected.

@paramoptions`InjectOptions & { optional?: false | undefined; }`

Control how injection is executed. Options correspond to injection strategies that can be specified with parameter decorators [`@Host`](/api/core/Host), [`@Self`](/api/core/Self), [`@SkipSelf`](/api/core/SkipSelf), and [`@Optional`](/api/core/Optional).

@returns`T`

the injected value if operation is successful.

@paramtoken`ProviderToken<T>`

A token that represents a dependency that should be injected.

@paramoptions`InjectOptions`

Control how injection is executed. Options correspond to injection strategies that can be specified with parameter decorators [`@Host`](/api/core/Host), [`@Self`](/api/core/Self), [`@SkipSelf`](/api/core/SkipSelf), and [`@Optional`](/api/core/Optional).

@returns`T | null`

the injected value if operation is successful, `null` if the token is not found and optional injection has been requested.

@paramtoken`HostAttributeToken`

A token that represents a static attribute on the host node that should be injected.

@returns`string`

Value of the attribute if it exists.

@paramtoken`HostAttributeToken`

A token that represents a static attribute on the host node that should be injected.

@paramoptions`{ optional: true; }`

@returns`string | null`

Value of the attribute if it exists, otherwise `null`.

@paramtoken`HostAttributeToken`

A token that represents a static attribute on the host node that should be injected.

@paramoptions`{ optional: false; }`

@returns`string`

Value of the attribute if it exists.

## [Usage Notes](#usage-notes)

In practice the [`inject()`](/api/core/inject) calls are allowed in a constructor, a constructor parameter and a field initializer:

```
@Injectable({providedIn: 'root'})
export class Car {
  radio: Radio|undefined;
  // OK: field initializer
  spareTyre = inject(Tyre);

  constructor() {
    // OK: constructor body
    this.radio = inject(Radio);
  }
}
```

It is also legal to call [`inject`](/api/core/inject) from a provider's factory:

```
providers: [
  {provide: Car, useFactory: () => {
    // OK: a class factory
    const engine = inject(Engine);
    return new Car(engine);
  }}
]
```

Calls to the [`inject()`](/api/core/inject) function outside of the class creation context will result in error. Most notably, calls to [`inject()`](/api/core/inject) are disallowed after a class instance was created, in methods (including lifecycle hooks):

```
@Component({ ... })
export class CarComponent {
  ngOnInit() {
    // ERROR: too late, the component instance was already created
    const engine = inject(Engine);
    engine.start();
  }
}
```

Jump to details

----
url: https://angular.dev/api/forms/NG_ASYNC_VALIDATORS
----

```
    
      const NG_ASYNC_VALIDATORS: InjectionToken<readonly (Function | Validator)[]>;
    
    
```

## [Usage Notes](#usage-notes)

### [Provide a custom async validator directive](#provide-a-custom-async-validator-directive)

The following example implements the [`AsyncValidator`](/api/forms/AsyncValidator) interface to create an async validator directive with a custom error key.

```
@Directive({
  selector: '[customAsyncValidator]',
  providers: [{provide: NG_ASYNC_VALIDATORS, useExisting: CustomAsyncValidatorDirective, multi:
true}]
})
class CustomAsyncValidatorDirective implements AsyncValidator {
  validate(control: AbstractControl): Promise<ValidationErrors|null> {
    return Promise.resolve({'custom': true});
  }
}
```

Jump to details

----
url: https://angular.dev/cli/e2e
----

```
ng e2e [project] [options]
```

```
ng e [project] [options]
```

Builds and serves an Angular application, then runs end-to-end tests.

----
url: https://angular.dev/tutorials/first-app/11-details-page
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/10-routing)[](/tutorials/first-app/12-forms)

[](/tutorials/first-app/10-routing)[](/tutorials/first-app/12-forms)

This tutorial lesson demonstrates how to connect the details page to your app.

**IMPORTANT:** We recommend using your local environment to learn routing.

## [What you'll learn](/tutorials/first-app/11-details-page#what-youll-learn)

At the end of this lesson your application will have support for routing to the details page.

## [Conceptual preview of routing with route parameters](/tutorials/first-app/11-details-page#conceptual-preview-of-routing-with-route-parameters)

Each housing location has specific details that should be displayed when a user navigates to the details page for that item. To accomplish this goal, you will need to use route parameters.

Route parameters enable you to include dynamic information as a part of your route URL. To identify which housing location a user has clicked on you will use the `id` property of the `HousingLocation` type.

1. ### [Using `routerLink` for dynamic navigation](/tutorials/first-app/11-details-page#using-routerlink-for-dynamic-navigation)

   In lesson 10, you added a second route to `src/app/routes.ts` which includes a special segment that identifies the route parameter, `id`:

   ```
   'details/:id'
   ```

   In this case, `:id` is dynamic and will change based on how the route is requested by the code.

   1. In `src/app/housing-location/housing-location.ts`, add an anchor tag to the `section` element and include the [`routerLink`](/api/router/RouterLinkWithHref) directive:

      ```
      import {Component, input} from '@angular/core';
      import {HousingLocationInfo} from '../housinglocation';
      import {RouterLink} from '@angular/router';

      @Component({
        selector: 'app-housing-location',
        imports: [RouterLink],
        template: `
          <section class="listing">
            <img
              class="listing-photo"
              [src]="housingLocation().photo"
              alt="Exterior photo of {{ housingLocation().name }}"
              crossorigin
            />
            <h2 class="listing-heading">{{ housingLocation().name }}</h2>
            <p class="listing-location">{{ housingLocation().city }}, {{ housingLocation().state }}</p>
            <a [routerLink]="['/details', housingLocation().id]">Learn More</a>
          </section>
        `,
        styleUrls: ['./housing-location.css'],
      })
      export class HousingLocation {
        housingLocation = input.required<HousingLocationInfo>();
      }
      ```

      The [`routerLink`](/api/router/RouterLinkWithHref) directive enables Angular's router to create dynamic links in the application. The value assigned to the [`routerLink`](/api/router/RouterLinkWithHref) is an array with two entries: the static portion of the path and the dynamic data.

      For the [`routerLink`](/api/router/RouterLinkWithHref) to work in the template, add a file level import of [`RouterLink`](/api/router/RouterLink) from '@angular/router', then update the component `imports` array to include [`RouterLink`](/api/router/RouterLink).

   2. At this point you can confirm that the routing is working in your app. In the browser, refresh the home page and click the "Learn More" button for a housing location.

2. ### [Get route parameters](/tutorials/first-app/11-details-page#get-route-parameters)

   In this step, you will get the route parameter in the `Details`. Currently, the app displays `details works!`. Next you'll update the code to display the `id` value passed using the route parameters.

   1. In `src/app/details/details.ts` update the template to import the functions, classes and services that you'll need to use in the `Details`:

   2. Update the `template` property of the [`@Component`](/api/core/Component) decorator to display the value `housingLocationId`:

      ```
      template: `<p>details works! {{ housingLocationId }}</p>`,
      ```

   3. Update the body of the `Details` class with the following code:

      ```
      export class Details {
         route: ActivatedRoute = inject(ActivatedRoute);
         housingLocationId = -1;
         constructor() {
         this.housingLocationId = Number(this.route.snapshot.params['id']);
         }
      }
      ```

      This code gives the `Details` access to the [`ActivatedRoute`](/api/router/ActivatedRoute) router feature that enables you to have access to the data about the current route. In the `constructor`, the code converts the `id` parameter acquired from the route from a string to a number.

   4. Save all changes.

   5. In the browser, click on one of the housing location's "Learn More" links and confirm that the numeric value displayed on the page matches the `id` property for that location in the data.

3. ### [Customize the `Details`](/tutorials/first-app/11-details-page#customize-the-details)

   Now that routing is working properly in the application this is a great time to update the template of the `Details` to display the specific data represented by the housing location for the route parameter.

   To access the data you will add a call to the `HousingService`.

   1. Update the template code to match the following code:

      Notice that the `housingLocation` properties are being accessed with the optional chaining operator `?`. This ensures that if the `housingLocation` value is null or undefined the application doesn't crash.

   2. Update the body of the `Details` class to match the following code:

      Now the component has the code to display the correct information based on the selected housing location. The `constructor` now includes a call to the `HousingService` to pass the route parameter as an argument to the `getHousingLocationById` service function.

   3. Copy the following styles into the `src/app/details/details.css` file:

      and save your changes

   4. In `Details` use the just created `details.css` file as the source for the styles:

   5. In the browser refresh the page and confirm that when you click on the "Learn More" link for a given housing location the details page displays the correct information based on the data for that selected item.

4. ### [Check navigation in the `Home`](/tutorials/first-app/11-details-page#check-navigation-in-the-home)

   In a previous lesson you updated the `App` template to include a [`routerLink`](/api/router/RouterLinkWithHref). Adding that code updated your app to enable navigation back to the `Home` whenever the logo is clicked.

   1. Confirm that your code matches the following:

      Your code should already be up-to-date but confirm to be sure.

**SUMMARY:** In this lesson you added routing to show details pages.

You now know how to:

* use route parameters to pass data to a route
* use the [`routerLink`](/api/router/RouterLinkWithHref) directive to use dynamic data to create a route
* use route parameter to retrieve data from the `HousingService` to display specific housing location information.

Really great work so far.

For more information about the topics covered in this lesson, visit:

Copy link to example

Expand example

----
url: https://angular.dev/tutorials/signals/4-managing-async-data-with-signals
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/3-deriving-state-with-linked-signals)[](/tutorials/signals/5-component-communication-with-signals)

[](/tutorials/signals/3-deriving-state-with-linked-signals)[](/tutorials/signals/5-component-communication-with-signals)

Now that you've learned [how to derive state with linked signals](/tutorials/signals/3-deriving-state-with-linked-signals), let's explore how to handle asynchronous data with the Resource API. The Resource API provides a powerful way to manage async operations using signals, with built-in loading states, error handling, and request management.

In this activity, you'll learn how to use the [`resource()`](/api/core/resource) function to load data asynchronously and how to handle different states of async operations by building a user profile loader that demonstrates the Resource API in action.

***

1. ### [Import resource function and API](#import-resource-function-and-api)

   Add [`resource`](/api/core/resource) to your existing imports and import the fake API function.

   ```
   // Add resource to existing imports
   import {Component, signal, computed, resource, ChangeDetectionStrategy} from '@angular/core';
   // Import mock API function
   import {getUserData} from './user-api';
   ```

2. ### [Create a resource for user data](#create-a-resource-for-user-data)

   Add a property in the component class that creates a resource to load user data based on a user ID signal.

   ```
   userId = signal(1);

   userResource = resource({
     params: () => ({id: this.userId()}),
     loader: (params) => getUserData(params.params.id),
   });
   ```

3. ### [Add methods to interact with the resource](#add-methods-to-interact-with-the-resource)

   Add methods to change the user ID and reload the resource.

   ```
   loadUser(id: number) {
     this.userId.set(id);
   }

   reloadUser() {
     this.userResource.reload();
   }
   ```

   Changing the params signal automatically triggers a reload, or you can manually reload with `reload()`.

4. ### [Create computed signals for resource states](#create-computed-signals-for-resource-states)

   Add computed signals to access different states of the resource.

   ```
   isLoading = this.userResource.isLoading;
   hasError = computed(() => this.userResource.status() === 'error');
   ```

   Resources provide a `status()` signal that can be 'loading', 'reloading', 'resolved', or 'error', a `value()` signal for the loaded data, and a `hasValue()` method that safely checks if data is available. The `isLoading()` signal is true for both 'loading' and 'reloading'.

5. ### [Wire up the buttons and display resource states](#wire-up-the-buttons-and-display-resource-states)

   The template structure is already provided. Now connect everything:

   Part 1. **Add click handlers to the buttons:**

   ```
   <button (click)="loadUser(1)">Load User 1</button>
   <button (click)="loadUser(2)">Load User 2</button>
   <button (click)="loadUser(999)">Load Invalid User</button>
   <button (click)="reloadUser()">Reload</button>
   ```

   Part 2. **Replace the placeholder with resource state handling:**

   ```
   @if (isLoading()) {
     <p>Loading user...</p>
   } @else if (hasError()) {
     <p class="error">Error: {{ userResource.error()?.message }}</p>
   } @else if (userResource.hasValue()) {
     <div class="user-info">
       <h3>{{ userResource.value().name }}</h3>
       <p>{{ userResource.value().email }}</p>
     </div>
   }
   ```

   The resource provides different methods to check its state:

   * `isLoading()` - true when fetching data
   * `hasError()` - true when an error occurred
   * `userResource.hasValue()` - true when data is available
   * `userResource.value()` - access the loaded data
   * `userResource.error()` - access error information

Excellent! You've now learned how to use the Resource API with signals. Key concepts to remember:

* **Resources are reactive**: They automatically reload when params change
* **Built-in state management**: Resources provide `status()`, `value()`, and `error()` signals
* **Automatic cleanup**: Resources handle request cancellation and cleanup automatically
* **Manual control**: You can manually reload or abort requests when needed

In the next lesson, you'll learn [how to pass data to components with input signals](/tutorials/signals/5-component-communication-with-signals)!

----
url: https://angular.dev/api/common/http/HttpParams
----

```
    
      class HttpParams {}
    
    
```

@paramoptions`HttpParamsOptions`

@returns`HttpParams`

Reports whether the body includes one or more values for a given parameter.

@paramparam`string`

The parameter name.

@returns`boolean`

True if the parameter has one or more values, false if it has no value or is not present.

Retrieves the first value for a parameter.

@paramparam`string`

The parameter name.

@returns`string | null`

The first value of the given parameter, or `null` if the parameter is not present.

Retrieves all values for a parameter.

@paramparam`string`

The parameter name.

@returns`string[] | null`

All values in a string array, or `null` if the parameter not present.

Retrieves all the parameters for this body.

@returns`string[]`

The parameter names in a string array.

Appends a new value to existing values for a parameter.

@paramparam`string`

The parameter name.

@paramvalue`string | number | boolean`

The new value to add.

@returns`HttpParams`

Constructs a new body with appended values for the given parameter name.

@paramparams`{ [param: string]: string | number | boolean | readonly (string | number | boolean)[]; }`

parameters and values

@returns`HttpParams`

Replaces the value for a parameter.

@paramparam`string`

The parameter name.

@paramvalue`string | number | boolean`

The new value.

@returns`HttpParams`

Removes a given value or all values from a parameter.

@paramparam`string`

The parameter name.

@paramvalue`string | number | boolean | undefined`

The value to remove, if provided.

@returns`HttpParams`

Serializes the body to an encoded string, where key-value pairs (separated by `=`) are separated by `&`s.

@returns`string`

## [Description](#description)

An HTTP request/response body that represents serialized parameters, per the MIME type `application/x-www-form-urlencoded`.

This class is immutable; all mutation operations return a new instance.

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/ToSignalOptions
----

```
    
      interface ToSignalOptions<T> {}
    
    
```

Initial value for the signal produced by [`toSignal`](/api/core/rxjs-interop/toSignal).

This will be the value of the signal until the observable emits its first value.

Whether to require that the observable emits synchronously when [`toSignal`](/api/core/rxjs-interop/toSignal) subscribes.

If this is `true`, [`toSignal`](/api/core/rxjs-interop/toSignal) will assert that the observable produces a value immediately upon subscription. Setting this option removes the need to either deal with `undefined` in the signal type or provide an `initialValue`, at the cost of a runtime error if this requirement is not met.

`Injector` which will provide the `DestroyRef` used to clean up the Observable subscription.

If this is not provided, a `DestroyRef` will be retrieved from the current [injection context](guide/di/dependency-injection-context), unless manual cleanup is requested.

Whether the subscription should be automatically cleaned up (via `DestroyRef`) when [`toSignal`](/api/core/rxjs-interop/toSignal)'s creation context is destroyed.

If manual cleanup is enabled, then `DestroyRef` is not used, and the subscription will persist until the `Observable` itself completes.

A comparison function which defines equality for values emitted by the observable.

Equality comparisons are executed against the initial value if one is provided.

A debug name for the signal. Used in Angular DevTools to identify the signal.

Jump to details

----
url: https://angular.dev/api/forms/MinLengthValidator
----

```
    
      class MinLengthValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the minimum length bound to this directive.

Determines whether this validator should be active or not based on an input. Base class implementation checks whether an input is defined (if the value is different from `null` and `undefined`). Validator classes that extend this base class can override this function with the logic specific to a particular validator directive.

@paraminput`unknown`

@returns`boolean`

## [Description](#description)

A directive that adds minimum length validation to controls marked with the `minlength` attribute. The directive is provided with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) multi-provider list.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Adding a minimum length validator](#adding-a-minimum-length-validator)

The following example shows how to add a minimum length validator to an input attached to an ngModel binding.

```
<input name="firstName" ngModel minlength="4">
```

Jump to details

----
url: https://angular.dev/api/router/PreloadingFeature
----

```
    
      type PreloadingFeature = RouterFeature<RouterFeatureKind.PreloadingFeature>
    
    
```

Jump to details

----
url: https://angular.dev/guide/forms/signals/validation
----

Forms need validation to ensure users provide correct, complete data before submission. Without validation, you would need to handle data quality issues on the server, provide poor user experience with unclear error messages, and manually check every constraint.

Signal Forms provides a schema-based validation approach. Validation rules bind to fields using a schema function, run automatically when values change, and expose errors through field state signals. This enables reactive validation that updates as users interact with the form.

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Component, signal} from '@angular/core';
import {email, form, FormField, required, submit} from '@angular/forms/signals';

interface LoginData {
  email: string;
  password: string;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [FormField],
})
export class App {
  loginModel = signal<LoginData>({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel, (schemaPath) => {
    required(schemaPath.email, {message: 'Email is required'});
    email(schemaPath.email, {message: 'Enter a valid email address'});

    required(schemaPath.password, {message: 'Password is required'});
  });

  onSubmit(event: Event) {
    event.preventDefault();
    submit(this.loginForm, {
      action: async () => {
        const credentials = this.loginModel();
        // In a real app, this would be async:
        // await this.authService.login(credentials);
        console.log('Logging in with:', credentials);
      },
    });
  }
}
```

### app.html

```
<form (submit)="onSubmit($event)">
  <div>
    <label>
      Email:
      <input type="email" [formField]="loginForm.email" />
    </label>

    @if (loginForm.email().touched() && loginForm.email().invalid()) {
      <ul class="error-list">
        @for (error of loginForm.email().errors(); track error) {
          <li>{{ error.message }}</li>
        }
      </ul>
    }
  </div>

  <div>
    <label>
      Password:
      <input type="password" [formField]="loginForm.password" />
    </label>

    @if (loginForm.password().touched() && loginForm.password().invalid()) {
      <ul class="error-list">
        @for (error of loginForm.password().errors(); track error) {
          <li>{{ error.message }}</li>
        }
      </ul>
    }
  </div>

  <button type="submit" [disabled]="loginForm().invalid()">Log In</button>
</form>
```

### app.css

```
form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 400px;
  padding: 1rem;
  font-family:
    Inter,
    system-ui,
    -apple-system,
    sans-serif;
}

div {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-weight: 500;
}

input {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
}

input:focus {
  outline: none;
  border-color: #4285f4;
}

button {
  padding: 0.75rem 1.5rem;
  background-color: #4285f4;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
  cursor: pointer;
  transition: background-color 0.2s;
}

button:hover {
  background-color: #357ae8;
}

button:active {
  background-color: #2a65c8;
}

.error-list {
  color: red;
  font-size: 0.875rem;
  margin: 0.25rem 0 0 0;
  padding-left: 0;
  list-style-position: inside;
}

.error-list li {
  margin: 0;
}
```

## [Validation basics](#validation-basics)

Validation in Signal Forms is defined through a schema function passed as the second argument to [`form()`](/api/forms/signals/form).

### [The schema function](#the-schema-function)

The schema function receives a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) object that lets you define your validation rules:

### app.ts

```
/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import {Component, signal} from '@angular/core';
import {email, form, FormField, required, submit} from '@angular/forms/signals';

interface LoginData {
  email: string;
  password: string;
}

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [FormField],
})
export class App {
  loginModel = signal<LoginData>({
    email: '',
    password: '',
  });

  loginForm = form(this.loginModel, (schemaPath) => {
    required(schemaPath.email, {message: 'Email is required'});
    email(schemaPath.email, {message: 'Enter a valid email address'});

    required(schemaPath.password, {message: 'Password is required'});
  });

  onSubmit(event: Event) {
    event.preventDefault();
    submit(this.loginForm, {
      action: async () => {
        const credentials = this.loginModel();
        // In a real app, this would be async:
        // await this.authService.login(credentials);
        console.log('Logging in with:', credentials);
      },
    });
  }
}
```

The schema function runs once during form initialization. Validation rules bind to fields using the schema path parameter (such as `schemaPath.email`, `schemaPath.password`), and validation runs automatically whenever field values change.

**NOTE:** The schema callback parameter (`schemaPath` in these examples) is a [`SchemaPathTree`](/api/forms/signals/SchemaPathTree) object that provides paths to all fields in your form. You can name this parameter anything you like.

### [How validation works](#how-validation-works)

Validation in Signal Forms follows this pattern:

1. **Define validation rules in schema** - Bind validation rules to fields in the schema function
2. **Automatic execution** - Validation rules run when field values change
3. **Error propagation** - Validation errors are exposed through field state signals
4. **Reactive updates** - UI automatically updates when validation state changes

Validation runs on every value change for interactive fields. Hidden and disabled fields don't run validation - their validation rules are skipped until the field becomes interactive again.

### [Validation timing](#validation-timing)

Validation rules execute in this order:

1. **Synchronous validation** - All synchronous validation rules run when value changes
2. **Asynchronous validation** - Asynchronous validation rules run only after all synchronous validation rules pass
3. **Field state updates** - The `valid()`, `invalid()`, `errors()`, and `pending()` signals update

Synchronous validation rules (like `required()`, `email()`) complete immediately. Asynchronous validation rules (like [`validateHttp()`](/api/forms/signals/validateHttp)) may take time and set the `pending()` signal to `true` while executing.

All validation rules run on every change - validation doesn't short-circuit after the first error. If a field has both `required()` and `email()` validation rules, both run, and both can produce errors simultaneously.

### [Native HTML validation](#native-html-validation)

Signal Forms **does not** use the browser's built-in [constraint validation](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation) to run validation rules. This is by design: Signal Forms allows any component to act as a form control, not just native form elements.

Instead, your validation rules run entirely in Angular, and validation state is exposed through field state signals such as `valid()`, `invalid()`, and `errors()` rather than through the element's native validity state. The [`FormRoot` directive](guide/forms/signals/form-submission) also sets the `novalidate` attribute on the form element so the browser does not block submission.

When a field is bound to a native form element, some built-in validation rules mirror their constraint to the corresponding native attribute: `required()`, `min()`, `max()`, [`minLength()`](/api/forms/signals/minLength), and [`maxLength()`](/api/forms/signals/maxLength) set the `required`, `min`, `max`, [`minlength`](/api/forms/MinLengthValidator), and [`maxlength`](/api/forms/MaxLengthValidator) attributes when the element supports them. An exception is `pattern()`, which does not set the native `pattern` attribute. Signal Forms sets these attributes to control input behavior and to improve accessibility, **not to trigger native validation**.

**NOTE:** There is one case where Signal Forms reads native validity state: when the browser cannot parse a native input's value (for example, a partially typed date), Signal Forms reports it as a parse error. Even then, the error surfaces through the field's `errors()` signal like any other validation error. See [Value transformation](guide/forms/signals/custom-controls#value-transformation) for details.

**IMPORTANT:** Do not rely on native validity features such as the `:valid` and `:invalid` CSS pseudo-classes, the element's `validity` property, or `validationMessage`. The browser may report some inputs as invalid as a side effect of the mirrored attributes (for example, a number input whose value is below its `min`), but this behavior varies by validation rule and input type and is not a supported way to observe validation state.

To style controls based on validation state, bind classes to field state signals (see [Field state management](guide/forms/signals/field-state-management#conditional-error-display)) or configure automatic status classes (see [Automatic status classes](guide/forms/signals/migration#automatic-status-classes)).

## [Built-in validation rules](#built-in-validation-rules)

Signal Forms provides validation rules for common validation scenarios. All built-in validation rules accept an options object for custom error messages and conditional logic.

### [required()](#required)

The `required()` validation rule ensures a field has a value:

```
import {Component, signal} from '@angular/core';
import {form, FormField, required} from '@angular/forms/signals';

@Component({
  selector: 'app-registration',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Username
        <input [formField]="registrationForm.username" />
      </label>

      <label>
        Email
        <input type="email" [formField]="registrationForm.email" />
      </label>

      <button type="submit">Register</button>
    </form>
  `,
})
export class RegistrationComponent {
  registrationModel = signal({
    username: '',
    email: '',
  });

  registrationForm = form(this.registrationModel, (schemaPath) => {
    required(schemaPath.username, {message: 'Username is required'});
    required(schemaPath.email, {message: 'Email is required'});
  });
}
```

A field is considered "empty" when its value is one of the following, and non-empty for every other value — including `0` and the empty array `[]`:

| Condition                | Example     |
| ------------------------ | ----------- |
| Value is `null`          | `null`      |
| Value is `undefined`     | `undefined` |
| Value is an empty string | `''`        |
| Value is `false`         | `false`     |
| Value is `NaN`           | `NaN`       |

The last two are worth calling out:

* `false` is empty to follow the native semantics of `required` on `<input type="checkbox">`, where an unchecked box fails validation.
* `NaN` is empty because it is usually the result of a parsing error, and is not a valid number.

For conditional requirements, use the `when` option:

```
registrationForm = form(this.registrationModel, (schemaPath) => {
  required(schemaPath.promoCode, {
    message: 'Promo code is required for discounts',
    when: ({valueOf}) => valueOf(schemaPath.applyDiscount),
  });
});
```

The validation rule only runs when the `when` function returns `true`.

**NOTE:** `required` treats an empty array as present (valid), so use [`minLength()`](#minlength-and-maxlength) to enforce a minimum number of array items.

### [email()](#email)

The `email()` validation rule checks for valid email format:

```
import {Component, signal} from '@angular/core';
import {form, FormField, email} from '@angular/forms/signals';

@Component({
  selector: 'app-contact',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Your Email
        <input type="email" [formField]="contactForm.email" />
      </label>
    </form>
  `,
})
export class ContactComponent {
  contactModel = signal({email: ''});

  contactForm = form(this.contactModel, (schemaPath) => {
    email(schemaPath.email, {message: 'Please enter a valid email address'});
  });
}
```

The `email()` validation rule uses a standard email format regex. It accepts addresses like `user@example.com` but rejects malformed addresses like `user@` or `@example.com`.

### [min() and max()](#min-and-max)

The `min()` and `max()` validation rules work with numeric values:

```
import {Component, signal} from '@angular/core';
import {form, FormField, min, max} from '@angular/forms/signals';

@Component({
  selector: 'app-age-form',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Age
        <input type="number" [formField]="ageForm.age" />
      </label>

      <label>
        Rating (1-5)
        <input type="number" [formField]="ageForm.rating" />
      </label>
    </form>
  `,
})
export class AgeFormComponent {
  ageModel = signal({
    age: 0,
    rating: 0,
  });

  ageForm = form(this.ageModel, (schemaPath) => {
    min(schemaPath.age, 18, {message: 'You must be at least 18 years old'});
    max(schemaPath.age, 120, {message: 'Please enter a valid age'});

    min(schemaPath.rating, 1, {message: 'Rating must be at least 1'});
    max(schemaPath.rating, 5, {message: 'Rating cannot exceed 5'});
  });
}
```

You can use computed values for dynamic constraints:

```
ageForm = form(this.ageModel, (schemaPath) => {
  min(schemaPath.participants, () => this.minimumRequired(), {
    message: 'Not enough participants',
  });
});
```

### [minDate() and maxDate()](#mindate-and-maxdate)

The [`minDate()`](/api/forms/signals/minDate) and [`maxDate()`](/api/forms/signals/maxDate) validation rules work with `Date` values, such as a field bound to `<input type="date">`. They also set the `min` and `max` attributes of that input.

```
import {Component, signal} from '@angular/core';
import {form, FormField, minDate, maxDate} from '@angular/forms/signals';

@Component({
  selector: 'app-booking-form',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Check-in
        <input type="date" [formField]="bookingForm.checkIn" />
      </label>
    </form>
  `,
})
export class BookingFormComponent {
  bookingModel = signal<{checkIn: Date | null}>({checkIn: null});

  bookingForm = form(this.bookingModel, (schemaPath) => {
    minDate(schemaPath.checkIn, new Date('2026-06-01'), {message: 'Bookings open on June 1'});
    maxDate(schemaPath.checkIn, new Date('2026-08-31'), {message: 'Bookings close on August 31'});
  });
}
```

**IMPORTANT:** An `<input type="date">` reads and writes `Date` values at midnight UTC, whatever the user's time zone, because that is how the browser's `valueAsDate` works. Build the limits the same way, for example with `new Date('2026-06-01')` or `new Date(Date.UTC(2026, 5, 1))`. A `Date` created from local time, such as `new Date(2026, 5, 1)` or `new Date()`, is shifted by the user's UTC offset. Depending on the time zone, the `min` or `max` attribute can then be off by a day, and the limit date itself can be rejected.

To require a date that is not before today, use today's calendar date in the user's time zone, expressed at midnight UTC:

```
const now = new Date();
const today = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()));

minDate(schemaPath.checkIn, today, {message: 'Check-in cannot be in the past'});
```

Passing `new Date()` directly can reject today's date, because the value picked in the input is midnight UTC, which is earlier than the current time. This happens in UTC and in the time zones behind it.

### [minLength() and maxLength()](#minlength-and-maxlength)

The [`minLength()`](/api/forms/signals/minLength) and [`maxLength()`](/api/forms/signals/maxLength) validation rules work with strings and arrays:

```
import {Component, signal} from '@angular/core';
import {form, FormField, minLength, maxLength} from '@angular/forms/signals';

@Component({
  selector: 'app-password-form',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Password
        <input type="password" [formField]="passwordForm.password" />
      </label>

      <label>
        Bio
        <textarea [formField]="passwordForm.bio"></textarea>
      </label>
    </form>
  `,
})
export class PasswordFormComponent {
  passwordModel = signal({
    password: '',
    bio: '',
  });

  passwordForm = form(this.passwordModel, (schemaPath) => {
    minLength(schemaPath.password, 8, {message: 'Password must be at least 8 characters'});
    maxLength(schemaPath.password, 100, {message: 'Password is too long'});

    maxLength(schemaPath.bio, 500, {message: 'Bio cannot exceed 500 characters'});
  });
}
```

For strings, "length" means the number of characters. For arrays, "length" means the number of elements.

### [pattern()](#pattern)

The `pattern()` validation rule validates against a regular expression:

```
import {Component, signal} from '@angular/core';
import {form, FormField, pattern} from '@angular/forms/signals';

@Component({
  selector: 'app-phone-form',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Phone Number
        <input [formField]="phoneForm.phone" placeholder="555-123-4567" />
      </label>

      <label>
        Postal Code
        <input [formField]="phoneForm.postalCode" placeholder="12345" />
      </label>
    </form>
  `,
})
export class PhoneFormComponent {
  phoneModel = signal({
    phone: '',
    postalCode: '',
  });

  phoneForm = form(this.phoneModel, (schemaPath) => {
    pattern(schemaPath.phone, /^\d{3}-\d{3}-\d{4}$/, {
      message: 'Phone must be in format: 555-123-4567',
    });

    pattern(schemaPath.postalCode, /^\d{5}$/, {
      message: 'Postal code must be 5 digits',
    });
  });
}
```

Common patterns:

| Pattern Type     | Regular Expression      | Example      |
| ---------------- | ----------------------- | ------------ |
| Phone            | `/^\d{3}-\d{3}-\d{4}$/` | 555-123-4567 |
| Postal code (US) | `/^\d{5}$/`             | 12345        |
| Alphanumeric     | `/^[a-zA-Z0-9]+$/`      | abc123       |
| URL-safe         | `/^[a-zA-Z0-9_-]+$/`    | my-url\_123  |

## [Validation of array items](#validation-of-array-items)

Forms can include arrays of nested objects (for example, a list of order items). To apply validation rules to each item in an array, use [`applyEach()`](/api/forms/signals/applyEach) inside your schema function. [`applyEach()`](/api/forms/signals/applyEach) iterates the array path and supplies a path for each item where you can apply validators just like top-level fields.

```
import {Component, signal} from '@angular/core';
import {applyEach, FormField, form, min, required, SchemaPathTree} from '@angular/forms/signals';

type Item = {name: string; quantity: number};

interface Order {
  title: string;
  description: string;
  items: Item[];
}

function ItemSchema(item: SchemaPathTree<Item>) {
  required(item.name, {message: 'Item name is required'});
  min(item.quantity, 1, {message: 'Quantity must be at least 1'});
}

@Component(/* ... */)
export class OrderComponent {
  orderModel = signal<Order>({
    title: '',
    description: '',
    items: [{name: '', quantity: 0}],
  });

  orderForm = form(this.orderModel, (schemaPath) => {
    required(schemaPath.title);
    required(schemaPath.description);

    applyEach(schemaPath.items, ItemSchema);
  });
}
```

## [Validation errors](#validation-errors)

When validation rules fail, they produce error objects that describe what went wrong. Understanding error structure helps you provide clear feedback to users.

**NOTE:** This section covers the errors that validation rules produce. For displaying and using validation errors in your UI, see the [Field State Management guide](guide/forms/signals/field-state-management).

### [Error structure](#error-structure)

Each validation error object contains these properties:

| Property  | Description                                                              |
| --------- | ------------------------------------------------------------------------ |
| `kind`    | The validation rule that failed (e.g., "required", "email", "minLength") |
| `message` | Optional human-readable error message                                    |

Built-in validation rules automatically set the `kind` property. The `message` property is optional - you can provide custom messages through validation rule options.

### [Custom error messages](#custom-error-messages)

All built-in validation rules accept a `message` option for custom error text:

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, minLength} from '@angular/forms/signals';

@Component({
  selector: 'app-signup',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Username
        <input [formField]="signupForm.username" />
      </label>

      <label>
        Password
        <input type="password" [formField]="signupForm.password" />
      </label>
    </form>
  `,
})
export class SignupComponent {
  signupModel = signal({
    username: '',
    password: '',
  });

  signupForm = form(this.signupModel, (schemaPath) => {
    required(schemaPath.username, {
      message: 'Please choose a username',
    });

    required(schemaPath.password, {
      message: 'Password cannot be empty',
    });
    minLength(schemaPath.password, 12, {
      message: 'Password must be at least 12 characters for security',
    });
  });
}
```

Custom messages should be clear, specific, and tell users how to fix the problem. Instead of "Invalid input", use "Password must be at least 12 characters for security".

### [Multiple errors per field](#multiple-errors-per-field)

When a field has multiple validation rules, each validation rule runs independently and can produce an error:

```
signupForm = form(this.signupModel, (schemaPath) => {
  required(schemaPath.email, {message: 'Email is required'});
  email(schemaPath.email, {message: 'Enter a valid email address'});
  minLength(schemaPath.email, 5, {message: 'Email is too short'});
});
```

If the email field is empty, only the `required()` error appears. If the user types "a\@b", both `email()` and [`minLength()`](/api/forms/signals/minLength) errors appear. All validation rules run - validation doesn't stop after the first failure.

**TIP:** Use the `touched() && invalid()` pattern in your templates to prevent errors from appearing before users have interacted with a field. For comprehensive guidance on displaying validation errors, see the [Field State Management guide](guide/forms/signals/field-state-management#conditional-error-display).

## [Custom validation rules](#custom-validation-rules)

While built-in validation rules handle common cases, you'll often need custom validation logic for business rules, complex formats, or domain-specific constraints.

### [Using validate()](#using-validate)

The [`validate()`](/api/forms/signals/validate) function creates custom validation rules. It receives a validator function that accesses the field context and returns:

| Return Value          | Meaning          |
| --------------------- | ---------------- |
| Error object          | Value is invalid |
| `null` or `undefined` | Value is valid   |

```
import {Component, signal} from '@angular/core';
import {form, FormField, validate} from '@angular/forms/signals';

@Component({
  selector: 'app-url-form',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Website URL
        <input [formField]="urlForm.website" />
      </label>
    </form>
  `,
})
export class UrlFormComponent {
  urlModel = signal({website: ''});

  urlForm = form(this.urlModel, (schemaPath) => {
    validate(schemaPath.website, ({value}) => {
      if (!value().startsWith('https://')) {
        return {
          kind: 'https',
          message: 'URL must start with https://',
        };
      }

      return null;
    });
  });
}
```

The validator function receives a [`FieldContext`](/api/forms/signals/FieldContext) object with:

| Property        | Type       | Description                                 |
| --------------- | ---------- | ------------------------------------------- |
| `value`         | Signal     | Signal containing the current field value   |
| `state`         | FieldState | The field state reference                   |
| `fieldTree`     | FieldTree  | The field tree reference                    |
| `valueOf()`     | Method     | Get the value of another field by path      |
| `stateOf()`     | Method     | Get the state of another field by path      |
| `fieldTreeOf()` | Method     | Get the field tree of another field by path |
| `pathKeys`      | Signal     | Path keys from root to current field        |

**NOTE:** Child fields also have a `key` signal, and array item fields have both `key` and `index` signals.

Return an error object with `kind` and `message` when validation fails. Return `null` or `undefined` when validation passes.

### [Using validateTree()](#using-validatetree)

The [`validateTree()`](/api/forms/signals/validateTree) function creates custom validation rules that can target multiple fields or provide complex validation logic for a whole subtree.

```
import {Component, model} from '@angular/core';
import {form, FormField, validateTree} from '@angular/forms/signals';

interface User {
  firstName: string;
  lastName: string;
}

@Component({/* ... */})
export class UserFormComponent {
  readonly userModel = model<User>({
    firstName: '',
    lastName: '',
  });

  userForm = form(this.userModel, (path) => {
    validateTree(path, (ctx) => {
      if (ctx.valueOf(path.firstName).length < 5) {
        return {
          kind: 'minLength5',
          message: 'First name must be at least 5 characters',
          fieldTree: ctx.fieldTree.lastName,
        };
      }

      return null;
    });
  });
}
```

The [`validateTree()`](/api/forms/signals/validateTree) validator function receives the same [`FieldContext`](/api/forms/signals/FieldContext) object as [`validate()`](/api/forms/signals/validate).

### [Reusable validation rules](#reusable-validation-rules)

Create reusable validation rule functions by wrapping [`validate()`](/api/forms/signals/validate):

```
function url(path: SchemaPath<string>, options?: {message?: string}) {
  validate(path, ({value}) => {
    try {
      new URL(value());
      return null;
    } catch {
      return {
        kind: 'url',
        message: options?.message || 'Enter a valid URL',
      };
    }
  });
}

function phoneNumber(path: SchemaPath<string>, options?: {message?: string}) {
  validate(path, ({value}) => {
    const phoneRegex = /^\d{3}-\d{3}-\d{4}$/;

    if (!phoneRegex.test(value())) {
      return {
        kind: 'phoneNumber',
        message: options?.message || 'Phone must be in format: 555-123-4567',
      };
    }

    return null;
  });
}
```

You can use custom validation rules just like built-in validation rules:

```
urlForm = form(this.urlModel, (schemaPath) => {
  url(schemaPath.website, {message: 'Please enter a valid website URL'});
  phoneNumber(schemaPath.phone);
});
```

## [Cross-field validation](#cross-field-validation)

Cross-field validation compares or relates multiple field values.

A common scenario for cross-field validation is password confirmation:

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, minLength, validate} from '@angular/forms/signals';

@Component({
  selector: 'app-password-change',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        New Password
        <input type="password" [formField]="passwordForm.password" />
      </label>

      <label>
        Confirm Password
        <input type="password" [formField]="passwordForm.confirmPassword" />
      </label>

      <button type="submit">Change Password</button>
    </form>
  `,
})
export class PasswordChangeComponent {
  passwordModel = signal({
    password: '',
    confirmPassword: '',
  });

  passwordForm = form(this.passwordModel, (schemaPath) => {
    required(schemaPath.password, {message: 'Password is required'});
    minLength(schemaPath.password, 8, {message: 'Password must be at least 8 characters'});

    required(schemaPath.confirmPassword, {message: 'Please confirm your password'});

    validate(schemaPath.confirmPassword, ({value, valueOf}) => {
      const confirmPassword = value();
      const password = valueOf(schemaPath.password);

      if (confirmPassword !== password) {
        return {
          kind: 'passwordMismatch',
          message: 'Passwords do not match',
        };
      }

      return null;
    });
  });
}
```

The confirmation validation rule accesses the password field value using `valueOf(schemaPath.password)` and compares it to the confirmation value. This validation rule runs reactively - if either password changes, validation reruns automatically.

## [Conditional validation](#conditional-validation)

Sometimes a validation rule should apply only under certain conditions, such as validating a shipping address only when an order ships internationally, or applying a different set of rules to each variant of a union-typed field.

Because validation rules live in the schema function, you apply them conditionally with the same structural functions that compose schemas:

* Use [`applyWhen()`](guide/forms/signals/form-logic#conditional-logic-with-applywhen) to activate a group of rules based on reactive form state, including the values of other fields.
* Use [`applyWhenValue()`](guide/forms/signals/schemas#type-narrowing-with-applywhenvalue) to apply rules based on a field's own value. When the predicate is a type guard, the rules are typed to the narrowed value, which makes it the recommended way to validate discriminated unions and other variant types.

For complete examples, including reusable schemas and discriminated unions, see the [Schemas and schema composability guide](guide/forms/signals/schemas).

## [Async validation](#async-validation)

Async validation handles validation that requires external data sources, like checking username availability on a server or validating against an API.

### [Using validateHttp()](#using-validatehttp)

The [`validateHttp()`](/api/forms/signals/validateHttp) function performs HTTP-based validation:

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, validateHttp} from '@angular/forms/signals';

@Component({
  selector: 'app-username-form',
  imports: [FormField],
  template: `
    <form novalidate>
      <label>
        Username
        <input [formField]="usernameForm.username" />

        @if (usernameForm.username().pending()) {
          <span class="checking">Checking availability...</span>
        }
      </label>
    </form>
  `,
})
export class UsernameFormComponent {
  usernameModel = signal({username: ''});

  usernameForm = form(this.usernameModel, (schemaPath) => {
    required(schemaPath.username, {message: 'Username is required'});

    validateHttp(schemaPath.username, {
      request: ({value}) => `/api/check-username?username=${value()}`,
      onSuccess: (response: any) => {
        if (response.taken) {
          return {
            kind: 'usernameTaken',
            message: 'Username is already taken',
          };
        }
        return null;
      },
      onError: (error) => ({
        kind: 'networkError',
        message: 'Could not verify username availability',
      }),
    });
  });
}
```

The [`validateHttp()`](/api/forms/signals/validateHttp) validation rule:

1. Calls the URL or request returned by the `request` function
2. Maps the successful response to a validation error or `null` using `onSuccess`
3. Handles request failures (network errors, HTTP errors) using `onError`
4. Sets `pending()` to `true` while the request is in progress
5. Only runs after all synchronous validation rules pass

### [Pending state](#pending-state)

While async validation runs, the field's `pending()` signal returns `true`. Use this to show loading indicators:

```
@if (form.username().pending()) {
  <span class="spinner">Checking...</span>
}
```

The `valid()` signal returns `false` while validation is pending, even if there are no errors yet. The `invalid()` signal only returns `true` if errors exist.

## [Integration with schema validation libraries](#integration-with-schema-validation-libraries)

Signal Forms have built-in support for libraries that conform to [Standard Schema](https://standardschema.dev/) like [Zod](https://zod.dev/) or [Valibot](https://valibot.dev/). The integration is provided via the [`validateStandardSchema`](/api/forms/signals/validateStandardSchema) function. This allows you to use existing schemas while maintaining Signal Forms' reactive validation benefits.

```
import {form, validateStandardSchema} from '@angular/forms/signals';
import * as z from 'zod';

// Define your schema
const userSchema = z.object({
  email: z.email(),
  password: z.string().min(8),
});

// Use with Signal Forms
const userForm = form(signal({email: '', password: ''}), (schemaPath) => {
  validateStandardSchema(schemaPath, userSchema);
});
```

### [Dynamic schemas](#dynamic-schemas)

You can pass a signal instead of a static schema so the validation schema updates automatically when its dependencies change.

```
import {Component, computed, signal} from '@angular/core';
import {form, FormField, validateStandardSchema} from '@angular/forms/signals';
import z from 'zod';

@Component({/* ... */})
export class DynamicSchema {
  model = signal({document: '', type: 'dni'});

  // Schema reacts automatically to type changes
  schema = computed(() =>
    z.object({
      document:
        this.model().type === 'dni'
          ? z.string().length(8, 'DNI must be 8 digits')
          : z.string().min(12, 'Passport must be at least 12 characters'),
    }),
  );

  f = form(this.model, (p) => validateStandardSchema(p, () => this.schema()));
}
```

## [Next steps](#next-steps)

This guide covered creating and applying validation rules. Related guides explore other aspects of Signal Forms:

----
url: https://angular.dev/api/common/KeyValuePipe
----

```
{{ value_expression | keyvalue:compareFn }}
```

## [API](#api)

```
    
      class KeyValuePipe implements PipeTransform {
}
    
    
```

@paramdiffers`KeyValueDiffers`

@returns`KeyValuePipe`

@paraminput`ReadonlyMap<K, V>`

@paramcompareFn`((a: KeyValue<K, V>, b: KeyValue<K, V>) => number) | null | undefined`

@returns`KeyValue<K, V>[]`

@paraminput`Record<K, V> | ReadonlyMap<K, V>`

@paramcompareFn`((a: KeyValue<K, V>, b: KeyValue<K, V>) => number) | null | undefined`

@returns`KeyValue<K, V>[]`

@paraminput`Record<K, V>`

@paramcompareFn`((a: KeyValue<string, V>, b: KeyValue<string, V>) => number) | null | undefined`

@returns`KeyValue<string, V>[]`

@paraminput`null | undefined`

@paramcompareFn`((a: KeyValue<unknown, unknown>, b: KeyValue<unknown, unknown>) => number) | null | undefined`

@returns`null`

@paraminput`ReadonlyMap<K, V> | null | undefined`

@paramcompareFn`((a: KeyValue<K, V>, b: KeyValue<K, V>) => number) | null | undefined`

@returns`KeyValue<K, V>[] | null`

@paraminput`Record<K, V> | ReadonlyMap<K, V> | null | undefined`

@paramcompareFn`((a: KeyValue<K, V>, b: KeyValue<K, V>) => number) | null | undefined`

@returns`KeyValue<K, V>[] | null`

@paraminput`Record<K, V> | null | undefined`

@paramcompareFn`((a: KeyValue<string, V>, b: KeyValue<string, V>) => number) | null | undefined`

@returns`KeyValue<string, V>[] | null`

@paraminput`T`

@paramcompareFn`(T extends object ? (a: T[keyof T], b: T[keyof T]) => number : never) | undefined`

@returns`T extends object ? KeyValue<keyof T, T[keyof T]>[] : null`

## [Description](#description)

Transforms Object or Map into an array of key value pairs.

The output array will be ordered by keys. By default the comparator will be by Unicode point value. You can optionally pass a compareFn if your keys are complex types. Passing `null` as the compareFn will use natural ordering of the input.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

### [Examples](#examples)

This examples show how an Object or a Map can be iterated by ngFor with the use of this keyvalue pipe.

```
@Component({
  selector: 'keyvalue-pipe',
  imports: [KeyValuePipe],
  template: ` <span>
    <p>Object</p>
    @for (item of object | keyvalue; track item.key) {
      <div>{{ item.key }}:{{ item.value }}</div>
    }
    <p>Map</p>
    @for (item of map | keyvalue; track item.key) {
      <div>{{ item.key }}:{{ item.value }}</div>
    }
    <p>Natural order</p>
    @for (item of map | keyvalue: null; track item.key) {
      <div>{{ item.key }}:{{ item.value }}</div>
    }
  </span>`,
})
export class KeyValuePipeComponent {
  object: {[key: number]: string} = {2: 'foo', 1: 'bar'};
  map = new Map([
    [2, 'foo'],
    [1, 'bar'],
  ]);
}
```

Jump to details

----
url: https://angular.dev/guide/components/selectors
----

Every component defines a [CSS selector](https://developer.mozilla.org/docs/Web/CSS/CSS_selectors) that determines how the component is used:

```
@Component({
  selector: 'profile-photo',
  ...
})
export class ProfilePhoto { }
```

You use a component by creating a matching HTML element in the templates of *other* components:

```
@Component({
  template: `
    <profile-photo />
    <button>Upload a new profile photo</button>`,
  ...,
})
export class UserProfile { }
```

**Angular matches selectors statically at compile-time**. Changing the DOM at run-time, either via Angular bindings or with DOM APIs, does not affect the components rendered.

**An element can match exactly one component selector.** If multiple component selectors match a single element, Angular reports an error.

**Component selectors are case-sensitive.**

## [Types of selectors](#types-of-selectors)

Angular supports a limited subset of [basic CSS selector types](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) in component selectors:

| **Selector type**  | **Description**                                                                                                 | **Examples**                  |
| ------------------ | --------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| Type selector      | Matches elements based on their HTML tag name, or node name.                                                    | `profile-photo`               |
| Attribute selector | Matches elements based on the presence of an HTML attribute and, optionally, an exact value for that attribute. | `[dropzone]` `[type="reset"]` |
| Class selector     | Matches elements based on the presence of a CSS class.                                                          | `.menu-item`                  |

For attribute values, Angular supports matching an exact attribute value with the equals (`=`) operator. Angular does not support other attribute value operators.

Angular component selectors do not support combinators, including the [descendant combinator](https://developer.mozilla.org/docs/Web/CSS/Descendant_combinator) or [child combinator](https://developer.mozilla.org/docs/Web/CSS/Child_combinator).

Angular component selectors do not support specifying [namespaces](https://developer.mozilla.org/docs/Web/SVG/Namespaces_Crash_Course).

### [The `:not` pseudo-class](#the-not-pseudo-class)

Angular supports [the `:not` pseudo-class](https://developer.mozilla.org/docs/Web/CSS/:not). You can append this pseudo-class to any other selector to narrow which elements a component's selector matches. For example, you could define a `[dropzone]` attribute selector and prevent matching `textarea` elements:

```
@Component({
  selector: '[dropzone]:not(textarea)',
  ...
})
export class DropZone { }
```

Angular does not support any other pseudo-classes or pseudo-elements in component selectors.

### [Combining selectors](#combining-selectors)

You can combine multiple selectors by concatenating them. For example, you can match `<button>` elements that specify `type="reset"`:

```
@Component({
  selector: 'button[type="reset"]',
  ...
})
export class ResetButton { }
```

You can also define multiple selectors with a comma-separated list:

```
@Component({
  selector: 'drop-zone, [dropzone]',
  ...
})
export class DropZone { }
```

Angular creates a component for each element that matches *any* of the selectors in the list.

## [Choosing a selector](#choosing-a-selector)

The vast majority of components should use a custom element name as their selector. All custom element names should include a hyphen as described by [the HTML specification](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name). By default, Angular reports an error if it encounters a custom tag name that does not match any available components, preventing bugs due to mistyped component names.

See [Advanced component configuration](guide/components/advanced-configuration) for details on using [native custom elements](https://developer.mozilla.org/docs/Web/Web_Components) in Angular templates.

### [Selector prefixes](#selector-prefixes)

The Angular team recommends using a short, consistent prefix for all the custom components defined inside your project. For example, if you were to build YouTube with Angular, you might prefix your components with `yt-`, with components like `yt-menu`, `yt-player`, etc. Namespacing your selectors like this makes it immediately clear where a particular component comes from. By default, the Angular CLI uses `app-`.

**IMPORTANT:** Angular uses the `ng` selector prefix for its own framework APIs. Never use `ng` as a selector prefix for your own custom components.

### [When to use an attribute selector](#when-to-use-an-attribute-selector)

You should consider an attribute selector when you want to create a component on a standard native element. For example, if you want to create a custom button component, you can take advantage of the standard `<button>` element by using an attribute selector:

```
@Component({
  selector: 'button[yt-upload]',
   ...
})
export class YouTubeUploadButton { }
```

This approach allows consumers of the component to directly use all the element's standard APIs without extra work. This is especially valuable for ARIA attributes such as `aria-label`.

Angular does not report errors when it encounters custom attributes that don't match an available component. When using components with attribute selectors, consumers may forget to import the component or its NgModule, resulting in the component not rendering. See [Importing and using components](guide/components#imports-in-the-component-decorator) for more information.

Components that define attribute selectors should use lowercase, dash-case attributes. You can follow the same prefixing recommendation described above.

----
url: https://angular.dev/api/router/ResolveFn
----

```
    
      type ResolveFn<T> = (  route: ActivatedRouteSnapshot,  state: RouterStateSnapshot,) => MaybeAsync<T | RedirectCommand>
    
    
```

## [Description](#description)

Function type definition for a data provider.

A data provider can be used with the router to resolve data during navigation. The router waits for the data to be resolved before the route is finally activated.

A resolver can also redirect a [`RedirectCommand`](/api/router/RedirectCommand) and the Angular router will use it to redirect the current navigation to the new destination.

## [Usage Notes](#usage-notes)

The following example implements a function that retrieves the data needed to activate the requested route.

```
interface Hero {
  name: string;
}
@Injectable()
export class HeroService {
  getHero(id: string) {
    return {name: `Superman-${id}`};
  }
}

export const heroResolver: ResolveFn<Hero> = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  return inject(HeroService).getHero(route.paramMap.get('id')!);
};

bootstrapApplication(App, {
  providers: [
    provideRouter([
      {
        path: 'detail/:id',
        component: HeroDetailComponent,
        resolve: {hero: heroResolver},
      },
    ]),
  ],
});
```

And you can access to your resolved data from `HeroComponent`:

```
@Component({template: ''})
export class HeroDetailComponent {
  private activatedRoute = inject(ActivatedRoute);

  ngOnInit() {
    this.activatedRoute.data.subscribe(({hero}) => {
      // do something with your resolved data ...
    });
  }
}
```

If resolved data cannot be retrieved, you may want to redirect the user to a new page instead:

```
export const heroResolver: ResolveFn<Hero> = async (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const router = inject(Router);
  const heroService = inject(HeroService);
  try {
    return await heroService.getHero(route.paramMap.get('id')!);
  } catch {
    return new RedirectCommand(router.parseUrl('/404'));
  }
};
```

When both guard and resolvers are specified, the resolvers are not executed until all guards have run and succeeded. For example, consider the following route configuration:

```
{
 path: 'base'
 canActivate: [baseGuard],
 resolve: {data: baseDataResolver}
 children: [
  {
    path: 'child',
    canActivate: [childGuard],
    component: ChildComponent,
    resolve: {childData: childDataResolver}
   }
 ]
}
```

The order of execution is: baseGuard, childGuard, baseDataResolver, childDataResolver.

Jump to details

----
url: https://angular.dev/api/core/ExistingProvider
----

```
    
      interface ExistingProvider extends ExistingSansProvider {}
    
    
```

An injection token. Typically an instance of [`Type`](/api/core/Type) or [`InjectionToken`](/api/core/InjectionToken), but can be `any`.

When true, injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

Existing `token` to return. (Equivalent to `injector.get(useExisting)`)

## [Usage Notes](#usage-notes)

```
class Greeting {
          salutation = 'Hello';
        }

        class FormalGreeting extends Greeting {
          override salutation = 'Greetings';
        }

        const injector = Injector.create({
          providers: [
            {provide: FormalGreeting, deps: []},
            {provide: Greeting, useExisting: FormalGreeting},
          ],
        });

        expect(injector.get(Greeting).salutation).toEqual('Greetings');
        expect(injector.get(FormalGreeting).salutation).toEqual('Greetings');
        expect(injector.get(FormalGreeting)).toBe(injector.get(Greeting));
```

### [Multi-value example](#multi-value-example)

```
const locale = new InjectionToken<string[]>('locale');
        const injector = Injector.create({
          providers: [
            {provide: locale, multi: true, useValue: 'en'},
            {provide: locale, multi: true, useValue: 'sk'},
          ],
        });

        const locales: string[] = injector.get(locale);
        expect(locales).toEqual(['en', 'sk']);
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/DragDrop
----

Deprecation warning

Use the [`createDragRef`](/api/cdk/drag-drop/createDragRef) or [`createDropListRef`](/api/cdk/drag-drop/createDropListRef) function for better tree shaking. Will be removed in v23.

## [API](#api)

```
    
      class DragDrop {}
    
    
```

Turns an element into a draggable item.

@deprecated

Use the [`createDragRef`](/api/cdk/drag-drop/createDragRef) function that provides better tree shaking.

@paramelement`any`

Element to which to attach the dragging functionality.

@paramconfig`DragRefConfig | undefined`

Object used to configure the dragging behavior.

@returns`DragRef<T>`

Turns an element into a drop list.

@deprecated

Use the [`createDropListRef`](/api/cdk/drag-drop/createDropListRef) function that provides better tree shaking.

@paramelement`any`

Element to which to attach the drop list functionality.

@returns`DropListRef<T>`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/22-pipes
----

[](/tutorials/learn-angular/20-inject-based-di)[](/tutorials/learn-angular/23-pipes-format-data)

[](/tutorials/learn-angular/20-inject-based-di)[](/tutorials/learn-angular/23-pipes-format-data)

Pipes are functions that are used to transform data in templates. In general, pipes are "pure" functions that don't cause side effects. Angular has a number of helpful built-in pipes you can import and use in your components. You can also create a custom pipe.

**NOTE:** Learn more about [pipes in the in-depth guide](/guide/templates/pipes).

In this activity, you will import a pipe and use it in the template.

***

To use a pipe in a template include it in an interpolated expression. Check out this example:

```
import {UpperCasePipe} from '@angular/common';

@Component({
  ...
  template: `{{ loudMessage | uppercase }}`,
  imports: [UpperCasePipe],
})
export class App {
  loudMessage = 'we think you are doing great!'
}
```

Now, it's your turn to give this a try:

1. ### [Import the `LowerCase` pipe](#import-the-lowercase-pipe)

   First, update `app.ts` by adding the file level import for [`LowerCasePipe`](/api/common/LowerCasePipe) from `@angular/common`.

   ```
   import {LowerCasePipe} from '@angular/common';
   ```

2. ### [Add the pipe to the template imports](#add-the-pipe-to-the-template-imports)

   Next, update [`@Component()`](/api/core/Component) decorator `imports` to include a reference to [`LowerCasePipe`](/api/common/LowerCasePipe)

   ```
   @Component({
     ...
     imports: [LowerCasePipe]
   })
   ```

3. ### [Add the pipe to the template](#add-the-pipe-to-the-template)

   Finally, in `app.ts` update the template to include the `lowercase` pipe:

   ```
   template: `{{ username | lowercase }}`
   ```

Pipes can also accept parameters which can be used to configure their output. Find out more in the next activity.

P.S. you are doing great ⭐️

----
url: https://angular.dev/api/aria/grid/GRID_CELL
----

```
    
      const GRID_CELL: InjectionToken<GridCell>;
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/EventData
----

```
    
      type EventData = | string  | number  | boolean  | Function  | undefined  | null  | EventData[]  | {[key: string]: EventData}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/destroyPlatform
----

```
function destroyPlatform(): void;
```

@returns`void`

## [Description](#description)

Destroys the current Angular platform and all Angular applications on the page. Destroys all modules and listeners registered with the platform.

This function should not be used in a server environment, as it will be a no-op.

Jump to details

----
url: https://angular.dev/api/core/ViewChild
----

```
@ViewChild(
  selector: ProviderToken<unknown> | Function | string,
  opts?: { read?: any; static?: boolean },
);
```

## [Description](#description)

Property decorator that configures a view query. The change detector looks for the first element or the directive matching the selector in the view DOM. If the view DOM changes, and a new child matches the selector, the property is updated.

**Metadata Properties**:

* **selector** - The directive type or the name used for querying.
* **read** - Used to read a different token from the queried elements.
* **static** - `true` to resolve query results before change detection runs, `false` to resolve after change detection. Defaults to `false`.

The following selectors are supported.

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* A template reference variable as a string (e.g. query `<my-component #cmp></my-component>` with `@ViewChild('cmp')`)
* Any provider defined in the child component tree of the current component (e.g. `@ViewChild(SomeService) someService: SomeService`)
* Any provider defined through a string token (e.g. `@ViewChild('someToken') someTokenVal: any`)
* A [`TemplateRef`](/api/core/TemplateRef) (e.g. query `<ng-template></ng-template>` with `@ViewChild(TemplateRef) template;`)

The following values are supported by `read`:

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* Any provider defined on the injector of the component that is matched by the `selector` of this query
* Any provider defined through a string token (e.g. `{provide: 'token', useValue: 'val'}`)
* [`TemplateRef`](/api/core/TemplateRef), [`ElementRef`](/api/core/ElementRef), [`ViewContainerRef`](/api/core/ViewContainerRef), and [`Injector`](/api/core/Injector)

Difference between dynamic and static queries:

* Dynamic queries (`static: false`) - The query resolves before the `ngAfterViewInit()` callback is called. The result will be updated for changes to your view, such as changes to `ngIf` and `ngFor` blocks.
* Static queries (`static: true`) - The query resolves once the view has been created, but before change detection runs (before the `ngOnInit()` callback is called). The result, though, will never be updated to reflect changes to your view, such as changes to `ngIf` and `ngFor` blocks.

## [Usage Notes](#usage-notes)

### [Example 1](#example-1)

```
import {Component, Directive, input, signal, ViewChild} from '@angular/core';

@Directive({
  selector: 'pane',
})
export class Pane {
  id = input.required<string>();
}

@Component({
  selector: 'example-app',
  imports: [Pane],
  template: `
    @if (shouldShow()) {
      <pane id="1" />
    } @else {
      <pane id="2" />
    }

    <button (click)="toggle()">Toggle</button>

    <div>Selected: {{ selectedPane() }}</div>
  `,
})
export class ViewChildComp {
  @ViewChild(Pane)
  set pane(v: Pane) {
    setTimeout(() => {
      this.selectedPane.set(v.id());
    }, 0);
  }
  selectedPane = signal('');
  shouldShow = signal(true);
  toggle() {
    this.shouldShow.update((shouldShow) => !shouldShow);
  }
}
```

### [Example 2](#example-2)

```
import {AfterViewInit, Component, Directive, ViewChild} from '@angular/core';

@Directive({
  selector: 'child-directive',
})
export class ChildDirective {}

@Component({
  selector: 'someCmp',
  templateUrl: 'someCmp.html',
})
export class SomeCmp implements AfterViewInit {
  @ViewChild(ChildDirective) child!: ChildDirective;

  ngAfterViewInit() {
    // child is set
  }
}
```

Jump to details

----
url: https://angular.dev/api/common/PlatformNavigation
----

```
    
      abstract class PlatformNavigation implements Navigation {}
    
    
```

@returns`NavigationHistoryEntry[]`

@paramoptions`NavigationUpdateCurrentEntryOptions`

@returns`void`

@paramurl`string`

@paramoptions`any`

@returns`NavigationResult`

@paramoptions`any`

@returns`NavigationResult`

@paramkey`string`

@paramoptions`any`

@returns`NavigationResult`

@paramoptions`any`

@returns`NavigationResult`

@paramoptions`any`

@returns`NavigationResult`

@paramtype`unknown`

@paramlistener`unknown`

@paramoptions`unknown`

@returns`void`

@paramtype`unknown`

@paramlistener`unknown`

@paramoptions`unknown`

@returns`void`

@paramevent`Event`

@returns`boolean`

## [Description](#description)

This class wraps the platform Navigation API which allows server-specific and test implementations.

Browser support is limited, so this API may not be available in all environments, may contain bugs, and is experimental.

Jump to details

----
url: https://angular.dev/guide/forms/template-driven-forms
----

This tutorial shows you how to create a template-driven form. The control elements in the form are bound to data properties that have input validation. The input validation helps maintain data integrity and styling to improve the user experience.

Template-driven forms use [two-way data binding](guide/templates/two-way-binding) to update the data model in the component as changes are made in the template and vice versa.

### Template vs Reactive forms

Angular supports two design approaches for interactive forms. Template-driven forms allow you to use form-specific directives in your Angular template. Reactive forms provide a model-driven approach to building forms.

Template-driven forms are a great choice for small or simple forms, while reactive forms are more scalable and suitable for complex forms. For a comparison of the two approaches, see [Choosing an approach](guide/forms#choosing-an-approach)

You can build almost any kind of form with an Angular template — login forms, contact forms, and pretty much any business form. You can lay out the controls creatively and bind them to the data in your object model. You can specify validation rules and display validation errors, conditionally allow input from specific controls, trigger built-in visual feedback, and much more.

## [Objectives](#objectives)

This tutorial teaches you how to do the following:

* Build an Angular form with a component and template
* Use [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) to create two-way data bindings for reading and writing input-control values
* Provide visual feedback using special CSS classes that track the state of the controls
* Display validation errors to users and conditionally allow input from form controls based on the form status
* Share information across HTML elements using [template reference variables](guide/templates/variables#template-reference-variables)

## [Build a template-driven form](#build-a-template-driven-form)

Template-driven forms rely on directives defined in the [`FormsModule`](/api/forms/FormsModule).

| Directives                                | Details                                                                                                                                                                                                                                                                                                                           |
| :---------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`NgModel`](/api/forms/NgModel)           | Reconciles value changes in the attached form element with changes in the data model, allowing you to respond to user input with input validation and error handling.                                                                                                                                                             |
| [`NgForm`](/api/forms/NgForm)             | Creates a top-level [`FormGroup`](/api/forms/FormGroup) instance and binds it to a `<form>` element to track aggregated form value and validation status. As soon as you import [`FormsModule`](/api/forms/FormsModule), this directive becomes active by default on all `<form>` tags. You don't need to add a special selector. |
| [`NgModelGroup`](/api/forms/NgModelGroup) | Creates and binds a [`FormGroup`](/api/forms/FormGroup) instance to a DOM element.                                                                                                                                                                                                                                                |

### [Step overview](#step-overview)

In the course of this tutorial, you bind a sample form to data and handle user input using the following steps.

1. Build the basic form.

   * Define a sample data model
   * Include required infrastructure such as the [`FormsModule`](/api/forms/FormsModule)

2. Bind form controls to data properties using the [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) directive and two-way data-binding syntax.

   * Examine how [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) reports control states using CSS classes
   * Name controls to make them accessible to [`ngModel`](/api/forms/SelectMultipleControlValueAccessor)

3. Track input validity and control status using [`ngModel`](/api/forms/SelectMultipleControlValueAccessor).

   * Add custom CSS to provide visual feedback on the status
   * Show and hide validation-error messages

4. Respond to a native HTML button-click event by adding to the model data.

5. Handle form submission using the [`ngSubmit`](api/forms/NgForm#ngSubmit) output property of the form.

   * Disable the **Submit** button until the form is valid
   * After submit, swap out the finished form for different content on the page

## [Build the form](#build-the-form)

1. The provided sample application creates the `Actor` class which defines the data model reflected in the form.

   ### actor.ts

   ```
   export class Actor {
     constructor(
       public id: number,
       public name: string,
       public skill: string,
       public studio?: string,
     ) {}
   }
   ```

2. The form layout and details are defined in the `ActorFormComponent` class.

   ### actor-form.component.ts (v1)

   ```
   import {Component} from '@angular/core';

   import {Actor} from '../actor';
   import {FormsModule} from '@angular/forms';
   import {JsonPipe} from '@angular/common';

   @Component({
     selector: 'app-actor-form',
     templateUrl: './actor-form.component.html',
     imports: [FormsModule, JsonPipe],
   })
   export class ActorFormComponent {
     skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

     model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

     submitted = false;

     onSubmit() {
       this.submitted = true;
     }

   }
   ```

   The component's `selector` value of "app-actor-form" means you can drop this form in a parent template using the `<app-actor-form>` tag.

3. The following code creates a new actor instance, so that the initial form can show an example actor.

   ```
   const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
   console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"
   ```

   This demo uses dummy data for [`model`](/api/core/model) and `skills`. In a real app, you would inject a data service to get and save real data, or expose these properties as inputs and outputs.

4. The component enables the Forms feature by importing the [`FormsModule`](/api/forms/FormsModule) module.

   ```
   @Component({
     selector: 'app-actor-form',
     templateUrl: './actor-form.component.html',
     imports: [FormsModule, JsonPipe],
   })
   export class ActorFormComponent {
   ```

5. The form is displayed in the application layout defined by the root component's template.

   ### app.component.html

   ```
   <app-actor-form />
   ```

   The initial template defines the layout for a form with two form groups and a submit button. The form groups correspond to two properties of the Actor data model, name and studio. Each group has a label and a box for user input.

   * The **Name** `<input>` control element has the HTML5 `required` attribute
   * The **Studio** `<input>` control element does not because `studio` is optional

   The **Submit** button has some classes on it for styling. At this point, the form layout is all plain HTML5, with no bindings or directives.

6. The sample form uses some style classes from [Twitter Bootstrap](https://getbootstrap.com/css): `container`, `form-group`, `form-control`, and `btn`. To use these styles, the application's style sheet imports the library.

   ### styles.css

   ```
   @import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
   ```

7. The form requires that an actor's skill is chosen from a predefined list of `skills` maintained internally in `ActorFormComponent`. The Angular `@for` loop iterates over the data values to populate the `<select>` element.

   ### actor-form.component.html (skills)

   ```
   <div class="form-group">
     <label for="skill">Skill</label>
     <select class="form-control" id="skill" required>
       @for (skill of skills; track $index) {
         <option [value]="skill">{{ skill }}</option>
       }
     </select>
   </div>
   ```

If you run the application right now, you see the list of skills in the selection control. The input elements are not yet bound to data values or events, so they are still blank and have no behavior.

## [Bind input controls to data properties](#bind-input-controls-to-data-properties)

The next step is to bind the input controls to the corresponding `Actor` properties with two-way data binding, so that they respond to user input by updating the data model, and also respond to programmatic changes in the data by updating the display.

The [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) directive declared in the [`FormsModule`](/api/forms/FormsModule) lets you bind controls in your template-driven form to properties in your data model. When you include the directive using the syntax for two-way data binding, `[(ngModel)]`, Angular can track the value and user interaction of the control and keep the view synced with the model.

1. Edit the template file `actor-form.component.html`.
2. Find the `<input>` tag next to the **Name** label.
3. Add the [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) directive, using two-way data binding syntax `[(ngModel)]="..."`.

### actor-form.component.html (excerpt)

```
<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
TODO: remove this: {{ model.name }}
```

**HELPFUL:** This example has a temporary diagnostic interpolation after each input tag, `{{model.name}}`, to show the current data value of the corresponding property. The comment reminds you to remove the diagnostic lines when you have finished observing the two-way data binding at work.

### [Access the overall form status](#access-the-overall-form-status)

When you imported the [`FormsModule`](/api/forms/FormsModule) in your component, Angular automatically created and attached an [NgForm](api/forms/NgForm) directive to the `<form>` tag in the template (because [`NgForm`](/api/forms/NgForm) has the selector `form` that matches `<form>` elements).

To get access to the [`NgForm`](/api/forms/NgForm) and the overall form status, declare a [template reference variable](guide/templates/variables#template-reference-variables).

1. Edit the template file `actor-form.component.html`.

2. Update the `<form>` tag with a template reference variable, `#actorForm`, and set its value as follows.

   ### actor-form.component.html (excerpt)

   ```
   <form #actorForm="ngForm">
   ```

   The `actorForm` template variable is now a reference to the [`NgForm`](/api/forms/NgForm) directive instance that governs the form as a whole.

3. Run the app.

4. Start typing in the **Name** input box.

   As you add and delete characters, you can see them appear and disappear from the data model.

The diagnostic line that shows interpolated values demonstrates that values are really flowing from the input box to the model and back again.

### [Naming control elements](#naming-control-elements)

When you use `[(ngModel)]` on an element, you must define a `name` attribute for that element. Angular uses the assigned name to register the element with the [`NgForm`](/api/forms/NgForm) directive attached to the parent `<form>` element.

The example added a `name` attribute to the `<input>` element and set it to "name", which makes sense for the actor's name. Any unique value will do, but using a descriptive name is helpful.

1. Add similar `[(ngModel)]` bindings and `name` attributes to **Studio** and **Skill**.
2. You can now remove the diagnostic messages that show interpolated values.
3. To confirm that two-way data binding works for the entire actor model, add a new text binding with the [`json`](api/common/JsonPipe) pipe at the top to the component's template, which serializes the data to a string.

After these revisions, the form template should look like the following:

### actor-form.component.html (excerpt)

```
{{ model | json }}
<div class="form-group">
  <label for="name">Name</label>
  <input
    type="text"
    class="form-control"
    id="name"
    required
    [(ngModel)]="model.name"
    name="name"
  />
</div>

<div class="form-group">
  <label for="studio">Studio</label>
  <input
    type="text"
    class="form-control"
    id="studio"
    [(ngModel)]="model.studio"
    name="studio"
  />
</div>

<div class="form-group">
  <label for="skill">Skill</label>
  <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill">
    @for (skill of skills; track $index) {
      <option [value]="skill">{{ skill }}</option>
    }
  </select>
</div>
```

You'll notice that:

* Each `<input>` element has an `id` property. This is used by the `<label>` element's `for` attribute to match the label to its input control. This is a [standard HTML feature](https://developer.mozilla.org/docs/Web/HTML/Element/label).

* Each `<input>` element also has the required `name` property that Angular uses to register the control with the form.

When you have observed the effects, you can delete the `{{ model | json }}` text binding.

## [Track form states](#track-form-states)

Angular applies the `ng-submitted` class to `form` elements after the form has been submitted. This class can be used to change the form's style after it has been submitted.

## [Track control states](#track-control-states)

Adding the [`NgModel`](/api/forms/NgModel) directive to a control adds class names to the control that describe its state. These classes can be used to change a control's style based on its state.

The following table describes the class names that Angular applies based on the control's state.

| States                           | Class if true | Class if false |
| :------------------------------- | :------------ | :------------- |
| The control has been visited.    | `ng-touched`  | `ng-untouched` |
| The control's value has changed. | `ng-dirty`    | `ng-pristine`  |
| The control's value is valid.    | `ng-valid`    | `ng-invalid`   |

Angular also applies the `ng-submitted` class to `form` elements upon submission, but not to the controls inside the `form` element.

You use these CSS classes to define the styles for your control based on its status.

### [Observe control states](#observe-control-states)

To see how the classes are added and removed by the framework, open the browser's developer tools and inspect the `<input>` element that represents the actor name.

1. Using your browser's developer tools, find the `<input>` element that corresponds to the **Name** input box. You can see that the element has multiple CSS classes in addition to "form-control".

2. When you first bring it up, the classes indicate that it has a valid value, that the value has not been changed since initialization or reset, and that the control has not been visited since initialization or reset.

   ```
   <input class="form-control ng-untouched ng-pristine ng-valid" />
   ```

3. Take the following actions on the **Name** `<input>` box, and observe which classes appear.

   * Look but don't touch. The classes indicate that it is untouched, pristine, and valid.

   * Click inside the name box, then click outside it. The control has now been visited, and the element has the `ng-touched` class instead of the `ng-untouched` class.

   * Add slashes to the end of the name. It is now touched and dirty.

   * Erase the name. This makes the value invalid, so the `ng-invalid` class replaces the `ng-valid` class.

### [Create visual feedback for states](#create-visual-feedback-for-states)

The `ng-valid`/`ng-invalid` pair is particularly interesting, because you want to send a strong visual signal when the values are invalid. You also want to mark required fields.

You can mark required fields and invalid data at the same time with a colored bar on the left of the input box.

To change the appearance in this way, take the following steps.

1. Add definitions for the `ng-*` CSS classes.

2. Add these class definitions to a new `forms.css` file.

3. Add the new file to the project as a sibling to `index.html`:

   ### forms.css

   ```
   .ng-valid[required], .ng-valid.required  {
     border-left: 5px solid #42A948; /* green */
   }

   .ng-invalid:not(form)  {
     border-left: 5px solid #a94442; /* red */
   }
   ```

4. In the `index.html` file, update the `<head>` tag to include the new style sheet.

   ### index.html (styles)

   ```
   <link rel="stylesheet" href="assets/forms.css" />
   ```

### [Show and hide validation error messages](#show-and-hide-validation-error-messages)

The **Name** input box is required and clearing it turns the bar red. That indicates that something is wrong, but the user doesn't know what is wrong or what to do about it. You can provide a helpful message by checking for and responding to the control's state.

The **Skill** select box is also required, but it doesn't need this kind of error handling because the selection box already constrains the selection to valid values.

To define and show an error message when appropriate, take the following steps.

1. ### [Add a local reference to the input](#add-a-local-reference-to-the-input)

   Extend the `input` tag with a template reference variable that you can use to access the input box's Angular control from within the template. In the example, the variable is `#name="ngModel"`.

   The template reference variable (`#name`) is set to `"ngModel"` because that is the value of the [`NgModel.exportAs`](api/core/Directive#exportAs) property. This property tells Angular how to link a reference variable to a directive.

2. ### [Add the error message](#add-the-error-message)

   Add a `<div>` that contains a suitable error message.

3. ### [Make the error message conditional](#make-the-error-message-conditional)

   Show or hide the error message by binding properties of the `name` control to the message `<div>` element's `hidden` property.

4. ### actor-form.component.html (hidden-error-msg)

   ```
   <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
   ```

   ### [Add a conditional error message to name](#add-a-conditional-error-message-to-name)

   Add a conditional error message to the `name` input box, as in the following example.

   ### actor-form.component.html (excerpt)

   ```
   <label for="name">Name</label>
   <input
     type="text"
     class="form-control"
     id="name"
     required
     [(ngModel)]="model.name"
     name="name"
     #name="ngModel"
   />
   <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
     Name is required
   </div>
   ```

### Illustrating the "pristine" state

In this example, you hide the message when the control is either valid or *pristine*. Pristine means the user hasn't changed the value since it was displayed in this form. If you ignore the `pristine` state, you would hide the message only when the value is valid. If you arrive in this component with a new, blank actor or an invalid actor, you'll see the error message immediately, before you've done anything.

You might want the message to display only when the user makes an invalid change. Hiding the message while the control is in the `pristine` state achieves that goal. You'll see the significance of this choice when you add a new actor to the form in the next step.

## [Add a new actor](#add-a-new-actor)

This exercise shows how you can respond to a native HTML button-click event by adding to the model data. To let form users add a new actor, you will add a **New Actor** button that responds to a click event.

1. In the template, place a "New Actor" `<button>` element at the bottom of the form.

2. In the component file, add the actor-creation method to the actor data model.

   ### actor-form.component.ts (New Actor method)

   ```
   newActor() {
     this.model = new Actor(42, '', '');
   }
   ```

3. Bind the button's click event to an actor-creation method, `newActor()`.

   ### actor-form.component.html (New Actor button)

   ```
   <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
   ```

4. Run the application again and click the **New Actor** button.

   The form clears, and the *required* bars to the left of the input box are red, indicating invalid `name` and `skill` properties. Notice that the error messages are hidden. This is because the form is pristine; you haven't changed anything yet.

5. Enter a name and click **New Actor** again.

   Now the application displays a `Name is required` error message, because the input box is no longer pristine. The form remembers that you entered a name before clicking **New Actor**.

6. To restore the pristine state of the form controls, clear all of the flags imperatively by calling the form's `reset()` method after calling the `newActor()` method.

   ### actor-form.component.html (Reset the form)

   ```
   <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
     New Actor
   </button>
   ```

   Now clicking **New Actor** resets both the form and its control flags.

## [Submit the form with `ngSubmit`](#submit-the-form-with-ngsubmit)

The user should be able to submit this form after filling it in. The **Submit** button at the bottom of the form does nothing on its own, but it does trigger a form-submit event because of its type (`type="submit"`).

To respond to this event, take the following steps.

1. ### [Listen to ngOnSubmit](#listen-to-ngonsubmit)

   Bind the form's [`ngSubmit`](api/forms/NgForm#ngSubmit) event property to the actor-form component's `onSubmit()` method.

   ### actor-form.component.html (ngSubmit)

   ```
   <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
   ```

2. ### [Bind the disabled property](#bind-the-disabled-property)

   Use the template reference variable, `#actorForm` to access the form that contains the **Submit** button and create an event binding.

   You will bind the form property that indicates its overall validity to the **Submit** button's `disabled` property.

   ### actor-form.component.html (submit-button)

   ```
   <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
     Submit
   </button>
   ```

3. ### [Run the application](#run-the-application)

   Notice that the button is enabled — although it doesn't do anything useful yet.

4. ### [Delete the Name value](#delete-the-name-value)

   This violates the "required" rule, so it displays the error message —and notice that it also disables the **Submit** button.

   You didn't have to explicitly wire the button's enabled state to the form's validity. The [`FormsModule`](/api/forms/FormsModule) did this automatically when you defined a template reference variable on the enhanced form element, then referred to that variable in the button control.

### [Respond to form submission](#respond-to-form-submission)

To show a response to form submission, you can hide the data entry area and display something else in its place.

1. ### [Wrap the form](#wrap-the-form)

   Wrap the entire form in a `<div>` and bind its `hidden` property to the `ActorFormComponent.submitted` property.

   ### actor-form.component.html (excerpt)

   ```
   <div [hidden]="submitted">
       <h1>Actor Form</h1>
       <form (ngSubmit)="onSubmit()" #actorForm="ngForm">

       <!-- ... all of the form ... -->
     </form>
   </div>
   ```

   The main form is visible from the start because the `submitted` property is false until you submit the form, as this fragment from the `ActorFormComponent` shows:

   ### actor-form.component.ts (submitted)

   ```
   submitted = false;

   onSubmit() {
     this.submitted = true;
   }
   ```

   When you click the **Submit** button, the `submitted` flag becomes true and the form disappears.

2. ### [Add the submitted state](#add-the-submitted-state)

   To show something else while the form is in the submitted state, add the following HTML below the new `<div>` wrapper.

   ### actor-form.component.html (excerpt)

   ```
   <div [hidden]="!submitted">
     <h2>You submitted the following:</h2>
     <div class="row">
       <div class="col-xs-3">Name</div>
       <div class="col-xs-9">{{ model.name }}</div>
     </div>
     <div class="row">
       <div class="col-xs-3">Studio</div>
       <div class="col-xs-9">{{ model.studio }}</div>
     </div>
     <div class="row">
       <div class="col-xs-3">Skill</div>
       <div class="col-xs-9">{{ model.skill }}</div>
     </div>
     <br />
     <button type="button" class="btn btn-primary" (click)="submitted = false">Edit</button>
   </div>
   ```

   This `<div>`, which shows a read-only actor with interpolation bindings, appears only while the component is in the submitted state.

   The alternative display includes an *Edit* button whose click event is bound to an expression that clears the `submitted` flag.

3. ### [Test the Edit button](#test-the-edit-button)

   Click the *Edit* button to switch the display back to the editable form.

## [Summary](#summary)

The Angular form discussed in this page takes advantage of the following framework features to provide support for data modification, validation, and more.

* An Angular HTML form template
* A form component class with a [`@Component`](/api/core/Component) decorator
* Handling form submission by binding to the [`NgForm.ngSubmit`](/api/forms/NgForm#ngSubmit) event property
* Template-reference variables such as `#actorForm` and `#name`
* `[(ngModel)]` syntax for two-way data binding
* The use of `name` attributes for validation and form-element change tracking
* The reference variable's `valid` property on input controls indicates whether a control is valid or should show error messages
* Controlling the **Submit** button's enabled state by binding to [`NgForm`](/api/forms/NgForm) validity
* Custom CSS classes that provide visual feedback to users about controls that are not valid

Here's the code for the final version of the application:

### actor-form.component.ts

```
import {Component} from '@angular/core';

import {Actor} from '../actor';
import {FormsModule} from '@angular/forms';
import {JsonPipe} from '@angular/common';

@Component({
  selector: 'app-actor-form',
  templateUrl: './actor-form.component.html',
  imports: [FormsModule, JsonPipe],
})
export class ActorFormComponent {
  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];

  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');

  submitted = false;

  onSubmit() {
    this.submitted = true;
  }

  newActor() {
    this.model = new Actor(42, '', '');
  }
}
```

### actor-form.component.html

```
<div class="container">
  <div [hidden]="submitted">
    <h1>Actor Form</h1>
    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
      <div class="form-group">
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
          #name="ngModel"
        />
        <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
          Name is required
        </div>
      </div>

      <div class="form-group">
        <label for="studio">Studio Affiliation</label>
        <input
          type="text"
          class="form-control"
          id="studio"
          [(ngModel)]="model.studio"
          name="studio"
        />
      </div>

      <div class="form-group">
        <label for="skill">Skill</label>
        <select
          class="form-control"
          id="skill"
          required
          [(ngModel)]="model.skill"
          name="skill"
          #skill="ngModel"
        >
          @for (skill of skills; track $index) {
            <option [value]="skill">{{ skill }}</option>
          }
        </select>
        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">
          skill is required
        </div>
      </div>

      <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
        Submit
      </button>
      <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
        New Actor
      </button>
    </form>
  </div>

  <div [hidden]="!submitted">
    <h2>You submitted the following:</h2>
    <div class="row">
      <div class="col-xs-3">Name</div>
      <div class="col-xs-9">{{ model.name }}</div>
    </div>
    <div class="row">
      <div class="col-xs-3">Studio</div>
      <div class="col-xs-9">{{ model.studio }}</div>
    </div>
    <div class="row">
      <div class="col-xs-3">Skill</div>
      <div class="col-xs-9">{{ model.skill }}</div>
    </div>
    <br />
    <button type="button" class="btn btn-primary" (click)="submitted = false">Edit</button>
  </div>
</div>
```

### actor.ts

```
export class Actor {
  constructor(
    public id: number,
    public name: string,
    public skill: string,
    public studio?: string,
  ) {}
}
```

### app.component.html

```
<app-actor-form />
```

### app.component.ts

```
import {Component} from '@angular/core';
import {ActorFormComponent} from './actor-form/actor-form.component';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  imports: [ActorFormComponent],
})
export class AppComponent {}
```

### main.ts

```
import {bootstrapApplication} from '@angular/platform-browser';
import {AppComponent} from './app/app.component';
import {provideZoneChangeDetection} from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [provideZoneChangeDetection({eventCoalescing: true})],
}).catch((err) => console.error(err));
```

### forms.css

```
.ng-valid[required], .ng-valid.required  {
  border-left: 5px solid #42A948; /* green */
}

.ng-invalid:not(form)  {
  border-left: 5px solid #a94442; /* red */
}
```

----
url: https://angular.dev/api/common/Plural
----

[`getLocalePluralCase`](/api/common/getLocalePluralCase) is deprecated

## [API](#api)

```
    
      enum Plural {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/NavigationExtras
----

```
    
      interface NavigationExtras extends UrlCreationOptions, NavigationBehaviorOptions {}
    
    
```

Specifies a root URI to use for relative navigation.

For example, consider the following route configuration where the parent route has two children.

```
[{
  path: 'parent',
  component: ParentComponent,
  children: [{
    path: 'list',
    component: ListComponent
  },{
    path: 'child',
    component: ChildComponent
  }]
}]
```

The following `go()` function navigates to the `list` route by interpreting the destination URI as relative to the activated `child` route

```
@Component({...})
class ChildComponent {
  constructor(private router: Router, private route: ActivatedRoute) {}

  go() {
    router.navigate(['../list'], { relativeTo: this.route });
  }
}
```

A value of `null` or `undefined` indicates that the navigation commands should be applied relative to the root.

Sets query parameters to the URL.

```
// Navigate to /results?page=1
router.navigate(['/results'], { queryParams: { page: 1 } });
```

Sets the hash fragment for the URL.

```
// Navigate to /results#top
router.navigate(['/results'], { fragment: 'top' });
```

How to handle query parameters in the router link for the next navigation. One of:

* `preserve` : Preserve current parameters.
* `merge` : Merge new with current parameters.

The "preserve" option discards any new query params:

```
// from /view1?page=1 to/view2?page=1
router.navigate(['/view2'], { queryParams: { page: 2 },  queryParamsHandling: "preserve"
});
```

The "merge" option appends new query params to the params from the current URL:

```
// from /view1?page=1 to/view2?page=1&otherKey=2
router.navigate(['/view2'], { queryParams: { otherKey: 2 },  queryParamsHandling: "merge"
});
```

In case of a key collision between current parameters and those in the `queryParams` object, the new value is used.

When true, preserves the URL fragment for the next navigation

```
// Preserve fragment from /results#top to /view#top
router.navigate(['/view'], { preserveFragment: true });
```

How to handle a navigation request to the current URL.

This value is a subset of the options available in [`OnSameUrlNavigation`](/api/router/OnSameUrlNavigation) and will take precedence over the default value set for the [`Router`](/api/router/Router).

When true, navigates without pushing a new state into history.

```
// Navigate silently to /view
this.router.navigate(['/view'], { skipLocationChange: true });
```

When true, navigates while replacing the current state in history.

```
// Navigate to /view
this.router.navigate(['/view'], { replaceUrl: true });
```

----
url: https://angular.dev/api/router/withComponentInputBinding
----

```
function withComponentInputBinding(
  options?: ComponentInputBindingOptions,
): ComponentInputBindingFeature;
```

@paramoptions`ComponentInputBindingOptions`

@returns`ComponentInputBindingFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can enable the feature:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withComponentInputBinding())
    ]
  }
);
```

The router bindings information from any of the following sources:

* query parameters
* path and matrix parameters
* static route data
* data from resolvers

Duplicate keys are resolved in the same order from above, from least to greatest, meaning that resolvers have the highest precedence and override any of the other information from the route.

Importantly, when an input does not have an item in the route data with a matching key, this input is set to `undefined`. This prevents previous information from being retained if the data got removed from the route (i.e. if a query parameter is removed). Default values can be provided with a resolver on the route to ensure the value is always present or an input and use an input transform in the component.

Advanced example of how you can disable binding from certain sources:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withComponentInputBinding({queryParams: false}))
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/best-practices/performance/defer
----

Deferrable views, also known as `@defer` blocks, reduce the initial bundle size of your application by deferring the loading of code that is not strictly necessary for the initial rendering of a page. This often results in a faster initial load and improvement in Core Web Vitals (CWV), primarily Largest Contentful Paint (LCP) and Time to First Byte (TTFB).

To use this feature, you can declaratively wrap a section of your template in a @defer block:

```
@defer {
  <large-component />
}
```

The code for any components, directives, and pipes inside the `@defer` block is split into a separate JavaScript file and loaded only when necessary, after the rest of the template has been rendered.

Deferrable views support a variety of triggers, prefetching options, and sub-blocks for placeholder, loading, and error state management.

## [Which dependencies are deferred?](/best-practices/performance/defer#which-dependencies-are-deferred)

Components, directives, pipes, and any component CSS styles can be deferred when loading an application.

In order for the dependencies within a `@defer` block to be deferred, they need to meet two conditions:

1. **They must be standalone.** Non-standalone dependencies cannot be deferred and are still eagerly loaded, even if they are inside of `@defer` blocks.
2. **They cannot be referenced outside of `@defer` blocks within the same file.** If they are referenced outside the `@defer` block or referenced within ViewChild queries, the dependencies will be eagerly loaded.

The *transitive* dependencies of the components, directives and pipes used in the `@defer` block do not strictly need to be standalone; transitive dependencies can still be declared in an [`NgModule`](/api/core/NgModule) and participate in deferred loading.

Angular's compiler produces a [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) statement for each component, directive, and pipe used in the `@defer` block. The main content of the block renders after all the imports resolve. Angular does not guarantee any particular order for these imports.

## [How to manage different stages of deferred loading](/best-practices/performance/defer#how-to-manage-different-stages-of-deferred-loading)

`@defer` blocks have several sub blocks to allow you to gracefully handle different stages in the deferred loading process.

### [`@defer`](/best-practices/performance/defer#defer)

This is the primary block that defines the section of content that is lazily loaded. It is not rendered initially– deferred content loads and renders once the specified [trigger](/best-practices/performance/defer#controlling-deferred-content-loading-with-triggers) occurs or the `when` condition is met.

By default, a `@defer` block is triggered when the browser state becomes [idle](/guide/templates/defer#idle).

```
@defer {
  <large-component />
}
```

### [Show placeholder content with `@placeholder`](/best-practices/performance/defer#show-placeholder-content-with-placeholder)

By default, defer blocks do not render any content before they are triggered.

The `@placeholder` is an optional block that declares what content to show before the `@defer` block is triggered.

```
@defer {
  <large-component />
} @placeholder {
  <p>Placeholder content</p>
}
```

While optional, certain triggers may require the presence of either a `@placeholder` or a [template reference variable](/guide/templates/variables#template-reference-variables) to function. See the [Triggers](/best-practices/performance/defer#controlling-deferred-content-loading-with-triggers) section for more details.

Angular replaces placeholder content with the main content once loading is complete. You can use any content in the placeholder section including plain HTML, components, directives, and pipes. Keep in mind the *dependencies of the placeholder block are eagerly loaded*.

The `@placeholder` block accepts an optional parameter to specify the `minimum` amount of time that this placeholder should be shown after the placeholder content initially renders.

```
@defer {
  <large-component />
} @placeholder (minimum 500ms) {
  <p>Placeholder content</p>
}
```

This `minimum` parameter is specified in time increments of milliseconds (ms) or seconds (s). You can use this parameter to prevent fast flickering of placeholder content in the case that the deferred dependencies are fetched quickly.

### [Show loading content with `@loading`](/best-practices/performance/defer#show-loading-content-with-loading)

The `@loading` block is an optional block that allows you to declare content that is shown while deferred dependencies are loading. It replaces the `@placeholder` block once loading is triggered.

```
@defer {
  <large-component />
} @loading {
  <img alt="loading..." src="loading.gif" />
} @placeholder {
  <p>Placeholder content</p>
}
```

Its dependencies are eagerly loaded (similar to `@placeholder`).

The `@loading` block accepts two optional parameters to help prevent fast flickering of content that may occur when deferred dependencies are fetched quickly,:

* `minimum` - the minimum amount of time that this placeholder should be shown
* `after` - the amount of time to wait after loading begins before showing the loading template

```
@defer {
  <large-component />
} @loading (after 100ms; minimum 1s) {
  <img alt="loading..." src="loading.gif" />
}
```

Both parameters are specified in time increments of milliseconds (ms) or seconds (s). In addition, the timers for both parameters begin immediately after the loading has been triggered.

### [Show error state when deferred loading fails with `@error`](/best-practices/performance/defer#show-error-state-when-deferred-loading-fails-with-error)

The `@error` block is an optional block that displays if deferred loading fails. Similar to `@placeholder` and `@loading`, the dependencies of the @error block are eagerly loaded.

```
@defer {
  <large-component />
} @error {
  <p>Failed to load large component.</p>
}
```

## [Controlling deferred content loading with triggers](/best-practices/performance/defer#controlling-deferred-content-loading-with-triggers)

You can specify **triggers** that control when Angular loads and displays deferred content.

When a `@defer` block is triggered, it replaces placeholder content with lazily loaded content.

Multiple event triggers can be defined by separating them with a semicolon, `;` and will be evaluated as OR conditions.

There are two types of triggers: `on` and `when`.

### [`on`](/best-practices/performance/defer#on)

`on` specifies a condition for when the `@defer` block is triggered.

The available triggers are as follows:

| Trigger                                                        | Description                                                            |
| -------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [`idle`](/best-practices/performance/defer#idle)               | Triggers when the browser is idle. Supports an optional timeout.       |
| [`viewport`](/best-practices/performance/defer#viewport)       | Triggers when specified content enters the viewport                    |
| [`interaction`](/best-practices/performance/defer#interaction) | Triggers when the user interacts with specified element                |
| [`hover`](/best-practices/performance/defer#hover)             | Triggers when the mouse hovers over specified area                     |
| [`immediate`](/best-practices/performance/defer#immediate)     | Triggers immediately after non-deferred content has finished rendering |
| [`timer`](/best-practices/performance/defer#timer)             | Triggers after a specific duration                                     |

#### [`idle`](/best-practices/performance/defer#idle)

The `idle` trigger loads the deferred content once the browser has reached an idle state, based on requestIdleCallback. This is the default behavior with a defer block.

You can optionally specify a timeout in milliseconds that is passed to [`requestIdleCallback`](https://developer.mozilla.org/docs/Web/API/Window/requestIdleCallback). If the browser doesn't schedule the callback soon enough, the work will run no later than the specified timeout.

```
<!-- @defer (on idle) -->
@defer {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

<!-- With a 500ms timeout -->
@defer (on idle(500)) {
  <large-cmp />
}
```

##### [Customizing `idle` behavior](/best-practices/performance/defer#customizing-idle-behavior)

You can customize the `idle` trigger by providing your own [`IdleService`](/api/core/IdleService) implementation and registering it with [`provideIdleServiceWith`](/api/core/provideIdleServiceWith) in your application's providers.

```
@Service()
class CustomIdleService implements IdleService {
  requestOnIdle(callback: (deadline?: IdleDeadline) => void, options?: IdleRequestOptions): number {
    // Custom idle scheduling logic can be implemented here.
    return requestIdleCallback(callback, options);
  }

  cancelOnIdle(id: number) {
    // Implement custom idle cancellation here.
  }
}

bootstrapApplication(App, {
  providers: [provideIdleServiceWith(CustomIdleService)],
});
```

#### [`viewport`](/best-practices/performance/defer#viewport)

The `viewport` trigger loads the deferred content when the specified content enters the viewport using the [Intersection Observer API](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API). Observed content may be `@placeholder` content or an explicit element reference.

By default, the `@defer` watches for the placeholder entering the viewport. Placeholders used this way must have a single root element.

```
@defer (on viewport) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

Alternatively, you can specify a [template reference variable](/guide/templates/variables) in the same template as the `@defer` block as the element that is watched to enter the viewport. This variable is passed in as a parameter on the viewport trigger.

```
<div #greeting>Hello!</div>
@defer (on viewport(greeting)) {
  <greetings-cmp />
}
```

If you want to customize the options of the `IntersectionObserver`, the `viewport` trigger supports passing in an object literal. The literal supports all properties from the second parameter of `IntersectionObserver`, except for `root`. When using the object literal notation, you have to pass your trigger using the `trigger` property.

```
<div #greeting>Hello!</div>

<!-- With options and a trigger -->
@defer (on viewport({trigger: greeting, rootMargin: '100px', threshold: 0.5})) {
  <greetings-cmp />
}

<!-- With options and an implied trigger -->
@defer (on viewport({rootMargin: '100px', threshold: 0.5})) {
  <greetings-cmp />
} @placeholder {
  <div>Implied trigger</div>
}
```

#### [`interaction`](/best-practices/performance/defer#interaction)

The `interaction` trigger loads the deferred content when the user interacts with the specified element through `click` or `keydown` events.

By default, the placeholder acts as the interaction element. Placeholders used this way must have a single root element.

```
@defer (on interaction) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

Alternatively, you can specify a [template reference variable](/guide/templates/variables) in the same template as the `@defer` block as the element that is watched for interactions. This variable is passed in as a parameter on the interaction trigger.

```
<div #greeting>Hello!</div>
@defer (on interaction(greeting)) {
  <greetings-cmp />
}
```

#### [`hover`](/best-practices/performance/defer#hover)

The `hover` trigger loads the deferred content when the mouse has hovered over the triggered area through the `mouseover` and `focusin` events.

By default, the placeholder acts as the interaction element. Placeholders used this way must have a single root element.

```
@defer (on hover) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

Alternatively, you can specify a [template reference variable](/guide/templates/variables) in the same template as the `@defer` block as the element that is hovered over. This variable is passed in as a parameter on the hover trigger.

```
<div #greeting>Hello!</div>
@defer (on hover(greeting)) {
  <greetings-cmp />
}
```

#### [`immediate`](/best-practices/performance/defer#immediate)

The `immediate` trigger loads the deferred content immediately. This means that the deferred block loads as soon as all other non-deferred content has finished rendering.

```
@defer (on immediate) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

#### [`timer`](/best-practices/performance/defer#timer)

The `timer` trigger loads the deferred content after a specified duration.

```
@defer (on timer(500ms)) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

The duration parameter must be specified in milliseconds (`ms`) or seconds (`s`).

### [`when`](/best-practices/performance/defer#when)

The `when` trigger accepts a custom conditional expression and loads the deferred content when the condition becomes truthy.

```
@defer (when condition) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

This is a one-time operation– the `@defer` block does not revert back to the placeholder if the condition changes to a falsy value after becoming truthy.

## [Prefetching data with `prefetch`](/best-practices/performance/defer#prefetching-data-with-prefetch)

In addition to specifying a condition that determines when deferred content is shown, you can optionally specify a **prefetch trigger**. This trigger lets you load the JavaScript associated with the `@defer` block before the deferred content is shown.

Prefetching enables more advanced behaviors, such as letting you start to prefetch resources before a user has actually seen or interacted with a defer block, but might interact with it soon, making the resources available faster.

You can specify a prefetch trigger similarly to the block's main trigger, but prefixed with the `prefetch` keyword. The block's main trigger and prefetch trigger are separated with a semi-colon character (`;`).

In the example below, the prefetching starts when a browser becomes idle and the contents of the block is rendered only once the user interacts with the placeholder.

```
@defer (on interaction; prefetch on idle) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

<!-- Prefetching with a 500ms idle timeout -->
@defer (on interaction; prefetch on idle(500)) {
  <large-cmp />
}
```

## [Testing `@defer` blocks](/best-practices/performance/defer#testing-defer-blocks)

Angular provides TestBed APIs to simplify the process of testing `@defer` blocks and triggering different states during testing. By default, `@defer` blocks in tests play through like a defer block would behave in a real application. If you want to manually step through states, you can switch the defer block behavior to `Manual` in the TestBed configuration.

```
it('should render a defer block in different states', async () => {
  // configures the defer block behavior to start in "paused" state for manual control.
  TestBed.configureTestingModule({deferBlockBehavior: DeferBlockBehavior.Manual});
  @Component({
    // ...
    template: `
      @defer {
        <large-component />
      } @placeholder {
        Placeholder
      } @loading {
        Loading...
      }
    `,
  })
  class ExampleA {}
  // Create component fixture.
  const componentFixture = TestBed.createComponent(ExampleA);
  // Retrieve the list of all defer block fixtures and get the first block.
  const deferBlockFixture = (await componentFixture.getDeferBlocks())[0];
  // Renders placeholder state by default.
  expect(componentFixture.nativeElement.innerHTML).toContain('Placeholder');
  // Render loading state and verify rendered output.
  await deferBlockFixture.render(DeferBlockState.Loading);
  expect(componentFixture.nativeElement.innerHTML).toContain('Loading');
  // Render final state and verify the output.
  await deferBlockFixture.render(DeferBlockState.Complete);
  expect(componentFixture.nativeElement.innerHTML).toContain('large works!');
});
```

## [Does `@defer` work with `NgModule`?](/best-practices/performance/defer#does-defer-work-with-ngmodule)

`@defer` blocks are compatible with both standalone and NgModule-based components, directives and pipes. However, **only standalone components, directives and pipes can be deferred**. NgModule-based dependencies are not deferred and are included in the eagerly loaded bundle.

## [Compatibility between `@defer` blocks and Hot Module Reload (HMR)](/best-practices/performance/defer#compatibility-between-defer-blocks-and-hot-module-reload-hmr)

When Hot Module Replacement (HMR) is active, all `@defer` block chunks are fetched eagerly, overriding any configured triggers. To restore the standard trigger behavior, you must disable HMR by serving your application with the `--no-hmr` flag.

## [How does `@defer` work with server-side rendering (SSR) and static-site generation (SSG)?](/best-practices/performance/defer#how-does-defer-work-with-server-side-rendering-ssr-and-static-site-generation-ssg)

By default, when rendering an application on the server (either using SSR or SSG), defer blocks always render their `@placeholder` (or nothing if a placeholder is not specified) and triggers are not invoked. On the client, the content of the `@placeholder` is hydrated and triggers are activated.

To render the main content of `@defer` blocks on the server (both SSR and SSG), you can enable [the Incremental Hydration feature](/guide/incremental-hydration) and configure `hydrate` triggers for the necessary blocks.

## [Barrel files and lazy chunks](/best-practices/performance/defer#barrel-files-and-lazy-chunks)

If you're using `@defer` but not seeing a separate lazy chunk in your build output, check how you're importing the deferred component. Importing through a barrel file (`index.ts`) is a common culprit — bundlers see the barrel as a single module and keep all its exports together, so your component ends up in the main bundle regardless of `@defer`.

### index.ts

```
export {HeavyComponent} from './heavy.component';
export {OtherComponent} from './other.component';
```

### parent.component.ts

```
import {HeavyComponent} from './index'; // pulls in OtherComponent too

@Component({
  imports: [HeavyComponent],
  template: `@defer {
    <heavy-component />
  }`,
})
export class ParentComponent {}
```

The fix is straightforward — import directly from the component's own file:

```
import {HeavyComponent} from './heavy.component';
```

That's enough for the bundler to split it into its own chunk and load it lazily when the trigger fires.

## [Best practices for deferring views](/best-practices/performance/defer#best-practices-for-deferring-views)

### [Avoid cascading loads with nested `@defer` blocks](/best-practices/performance/defer#avoid-cascading-loads-with-nested-defer-blocks)

When you have nested `@defer` blocks, they should have different triggers in order to avoid loading simultaneously, which causes cascading requests and may negatively impact page load performance.

### [Avoid layout shifts](/best-practices/performance/defer#avoid-layout-shifts)

Avoid deferring components that are visible in the user’s viewport on initial load. Doing this may negatively affect Core Web Vitals by causing an increase in cumulative layout shift (CLS).

In the event this is necessary, avoid `immediate`, `timer`, `viewport`, and custom `when` triggers that cause the content to load during the initial page render.

### [Keep accessibility in mind](/best-practices/performance/defer#keep-accessibility-in-mind)

When using `@defer` blocks, consider the impact on users with assistive technologies like screen readers. Screen readers that focus on a deferred section will initially read the placeholder or loading content, but may not announce changes when the deferred content loads.

To ensure deferred content changes are announced to screen readers, you can wrap your `@defer` block in an element with a live region:

```
<div aria-live="polite" aria-atomic="true">
  @defer (on timer(2000)) {
    <user-profile [user]="currentUser" />
  } @placeholder {
    Loading user profile...
  } @loading {
    Please wait...
  } @error {
    Failed to load profile
  }
</div>
```

This ensures that changes are announced to the user when transitions (placeholder → loading → content/error) occur.

----
url: https://angular.dev/api/core/testing/fakeAsync
----

```
function fakeAsync(
  fn: Function,
  options?: { flush?: boolean | undefined } | undefined,
): (...args: any[]) => any;
```

@paramfn`Function`

The function that you want to wrap in the [`fakeAsync`](/api/core/testing/fakeAsync) zone.

@paramoptions`{ flush?: boolean | undefined; } | undefined`

* flush: When true, will drain the macrotask queue after the test function completes. When false, will throw an exception at the end of the function if there are pending timers.

@returns`(...args: any[]) => any`

The function wrapped to be executed in the [`fakeAsync`](/api/core/testing/fakeAsync) zone. Any arguments passed when calling this returned function will be passed through to the `fn` function in the parameters when it is called.

## [Description](#description)

**IMPORTANT:** This API requires Zone.js and cannot be used with the Vitest test runner

Wraps a function to be executed in the [`fakeAsync`](/api/core/testing/fakeAsync) zone:

* Microtasks are manually executed by calling [`flushMicrotasks()`](/api/core/testing/flushMicrotasks).
* Timers are synchronous; [`tick()`](/api/core/testing/tick) simulates the asynchronous passage of time.

Can be used to wrap [`inject()`](/api/core/testing/inject) calls.

## [Usage Notes](#usage-notes)

### [Example](#example)

```
describe('this test', () => {
  it(
    'looks async but is synchronous',
    <any>fakeAsync((): void => {
      let flag = false;
      setTimeout(() => {
        flag = true;
      }, 100);
      expect(flag).toBe(false);
      tick(50);
      expect(flag).toBe(false);
      tick(50);
      expect(flag).toBe(true);
    }),
  );
});
```

Jump to details

----
url: https://angular.dev/guide/routing/common-router-tasks
----

This guide covers some other common tasks associated with using Angular router in your application.

## [Getting route information](#getting-route-information)

Often, as a user navigates your application, you want to pass information from one component to another. For example, consider an application that displays a shopping list of grocery items. Each item in the list has a unique `id`. To edit an item, users click an Edit button, which opens an `EditGroceryItem` component. You want that component to retrieve the `id` for the grocery item so it can display the right information to the user.

Use a route to pass this type of information to your application components. To do so, you use the [`withComponentInputBinding`](/api/router/withComponentInputBinding) feature with [`provideRouter`](/api/router/provideRouter) or the `bindToComponentInputs` option of [`RouterModule.forRoot`](/api/router/RouterModule#forRoot).

To get information from a route:

1. ### [Add `withComponentInputBinding`](#add-withcomponentinputbinding)

   Add the [`withComponentInputBinding`](/api/router/withComponentInputBinding) feature to the [`provideRouter`](/api/router/provideRouter) method.

   ```
   providers: [provideRouter(appRoutes, withComponentInputBinding())];
   ```

2. ### [Add an `input` to the component](#add-an-input-to-the-component)

   Update the component to have an [`input()`](/api/core/input) property matching the name of the parameter.

   ```
   id = input.required<string>();
   hero = computed(() => this.service.getHero(this.id()));
   ```

3. ### [Optional: Use a default value](#optional-use-a-default-value)

   The router assigns values to all inputs based on the current route when [`withComponentInputBinding`](/api/router/withComponentInputBinding) is enabled. The router assigns `undefined` if no route data matches the input key, such as when an optional query parameter is missing. You should include `undefined` in the `input`'s type when there's a possibility that an input might not be matched by the route.

   Provide a default value by either using the `transform` option on the input or managing a local state with a [`linkedSignal`](/api/core/linkedSignal).

   ```
   id = input.required({
     transform: (maybeUndefined: string | undefined) => maybeUndefined ?? '0',
   });
   // or
   id = input<string | undefined>();
   internalId = linkedSignal(() => this.id() ?? getDefaultId());
   ```

**NOTE:** You can bind all route data with key-value pairs to component inputs: route resources, static or resolved route data, path parameters, matrix parameters, and query parameters.

### [Input binding priority](#input-binding-priority)

When multiple route sources define identical keys, the router resolves collisions in the following priority order (from highest to lowest):

1. **Route resources**: Values defined in the route's `resources` map. Blocking resources bind their unwrapped value (`resource.value()`), while non-blocking resources bind the [`Resource`](/api/core/Resource) instance.
2. **Route data and resolvers**: Static data defined in `data` or values resolved via `resolve`.
3. **Path parameters and matrix parameters**: Parameters from the URL path (such as `:id`) and matrix parameters.
4. **Query parameters**: Parameters from the query string (such as `?id=123`).

For example, if a route has both a path parameter `:id` and a query parameter `?id=...`, the path parameter value is bound to the component's `id` input. If the route also defines a resource named `id`, the resource value takes precedence over both.

### [Disable query parameter binding](#disable-query-parameter-binding)

Use [`ComponentInputBindingOptions`](/api/router/ComponentInputBindingOptions) to disable query parameter binding if you manage query parameters separately:

```
provideRouter(appRoutes, withComponentInputBinding({queryParams: false}));
```

### [Configure behavior for inputs not available in router data](#configure-behavior-for-inputs-not-available-in-router-data)

By default, the router sets an input to `undefined` if it was not available in the router data during a navigation. This ensures that stale data is not retained.

If you want to avoid setting `undefined` for inputs that have *never* been available in the router data for the active component instance, you can set the `unmatchedInputBehavior` option to `'undefinedIfStale'`:

```
provideRouter(appRoutes, withComponentInputBinding({unmatchedInputBehavior: 'undefinedIfStale'}));
```

When you combine `unmatchedInputBehavior: 'undefinedIfStale'` with `queryParams: false`, inputs retain their initial values unless they are explicitly provided by the router. The exception is matrix parameters: if a matrix parameter is provided in one navigation and removed in a subsequent one, the router will set the input to `undefined` to avoid retaining stale data.

```
provideRouter(
  appRoutes,
  withComponentInputBinding({
    queryParams: false,
    unmatchedInputBehavior: 'undefinedIfStale',
  }),
);
```

### [Inherit parent route data](#inherit-parent-route-data)

By default, child routes inherit parameters and data from parent routes (equivalent to `paramsInheritanceStrategy: 'always'`). This means you can access parent route info directly in child components.

If you need to restore the legacy behavior where parameters were only inherited from empty path routes, you can set `paramsInheritanceStrategy` to `'emptyOnly'`:

```
provideRouter(routes, withRouterConfig({paramsInheritanceStrategy: 'emptyOnly'}));
```

See [router configuration options](guide/routing/customizing-route-behavior#router-configuration-options) for details on other available settings.

## [Displaying a 404 page](#displaying-a-404-page)

To display a 404 page, set up a [wildcard route](guide/routing/define-routes#wildcards) with the `component` property set to the component you'd like to use for your 404 page as follows:

```
const routes: Routes = [
  {path: 'first-component', component: First},
  {path: 'second-component', component: Second},
  {path: '**', component: PageNotFound}, // Wildcard route for a 404 page
];
```

The last route with the `path` of `**` is a wildcard route. The router selects this route if the requested URL doesn't match any of the paths earlier in the list and sends the user to the `PageNotFound`.

## [Link parameters array](#link-parameters-array)

A link parameters array holds the following ingredients for router navigation:

* The path of the route to the destination component
* Required and optional route parameters that go into the route URL

Bind the [`RouterLink`](/api/router/RouterLink) directive to such an array like this:

```
<a [routerLink]="['/heroes']">Heroes</a>
```

The following is a two-element array when specifying a route parameter:

```
<a [routerLink]="['/hero', hero.id]">
  <span class="badge">{{ hero.id }}</span
  >{{ hero.name }}
</a>
```

Provide optional route parameters in an object, as in `{ foo: 'foo' }`:

```
<a [routerLink]="['/crisis-center', {foo: 'foo'}]">Crisis Center</a>
```

This syntax passes matrix parameters, which are optional parameters associated with a specific URL segment. Learn more about [matrix parameters](/guide/routing/read-route-state#matrix-parameters).

These three examples cover the needs of an application with one level of routing. However, with a child router, such as in the crisis center, you create new link array possibilities.

The following minimal [`RouterLink`](/api/router/RouterLink) example builds upon a specified default child route for the crisis center.

```
<a [routerLink]="['/crisis-center']">Crisis Center</a>
```

Review the following:

* The first item in the array identifies the parent route (`/crisis-center`)
* There are no parameters for this parent route
* There is no default for the child route so you need to pick one
* You're navigating to the `CrisisList`, whose route path is `/`, but you don't need to explicitly add the slash

Consider the following router link that navigates from the root of the application down to the Dragon Crisis:

```
<a [routerLink]="['/crisis-center', 1]">Dragon Crisis</a>
```

* The first item in the array identifies the parent route (`/crisis-center`)
* There are no parameters for this parent route
* The second item identifies the child route details about a particular crisis (`/:id`)
* The details child route requires an `id` route parameter
* You added the `id` of the Dragon Crisis as the second item in the array (`1`)
* The resulting path is `/crisis-center/1`

You could also redefine the `App` template with Crisis Center routes exclusively:

```
@Component({
  template: `
    <h1 class="title">Angular Router</h1>
    <nav>
      <a [routerLink]="['/crisis-center']">Crisis Center</a>
      <a [routerLink]="['/crisis-center/1', {foo: 'foo'}]">Dragon Crisis</a>
      <a [routerLink]="['/crisis-center/2']">Shark Crisis</a>
    </nav>
    <router-outlet />
  `,
})
export class App {}
```

In summary, you can write applications with one, two or more levels of routing. The link parameters array affords the flexibility to represent any routing depth and any legal sequence of route paths, (required) router parameters, and (optional) route parameter objects.

## [`LocationStrategy` and browser URL styles](#locationstrategy-and-browser-url-styles)

When the router navigates to a new component view, it updates the browser's location and history with a URL for that view.

Modern HTML5 browsers support [history.pushState](https://developer.mozilla.org/docs/Web/API/History_API/Working_with_the_History_API#adding_and_modifying_history_entries "HTML5 browser history push-state"), a technique that changes a browser's location and history without triggering a server page request. The router can compose a "natural" URL that is indistinguishable from one that would otherwise require a page load.

Here's the Crisis Center URL in this "HTML5 pushState" style:

```
localhost:3002/crisis-center
```

Older browsers send page requests to the server when the location URL changes unless the change occurs after a "#" (called the "hash"). Routers can take advantage of this exception by composing in-application route URLs with hashes. Here's a "hash URL" that routes to the Crisis Center.

```
localhost:3002/src/#/crisis-center
```

The router supports both styles with two [`LocationStrategy`](/api/common/LocationStrategy) providers:

| Providers                                                  | Details                              |
| :--------------------------------------------------------- | :----------------------------------- |
| [`PathLocationStrategy`](/api/common/PathLocationStrategy) | The default "HTML5 pushState" style. |
| [`HashLocationStrategy`](/api/common/HashLocationStrategy) | The "hash URL" style.                |

The [`RouterModule.forRoot()`](/api/router/RouterModule#forRoot) function sets the [`LocationStrategy`](/api/common/LocationStrategy) to the [`PathLocationStrategy`](/api/common/PathLocationStrategy), which makes it the default strategy. You also have the option of switching to the [`HashLocationStrategy`](/api/common/HashLocationStrategy) with an override during the bootstrapping process.

**HELPFUL:** For more information on providers and the bootstrap process, see [Dependency Injection](guide/di/defining-dependency-providers).

----
url: https://angular.dev/guide/aria/tabs
----

Tabs display layered content sections where only one panel is visible at a time. Users switch between panels by clicking tab buttons or using arrow keys to navigate the tab list.

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs>
  <div ngTabList selectionMode="follow" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 0.75rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab][aria-selected='false']:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
  border-bottom-right-radius: 0.5rem;
  border-bottom-left-radius: 0.5rem;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="material-tabs">
  <div ngTabList selectionMode="follow" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
    <div class="bottom-border"></div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab]:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--bright-blue);
}

.bottom-border {
  position: absolute;
  pointer-events: none;
  left: 0;
  bottom: 0;
  height: 3px;
  width: calc(100% / 3);
  background-color: var(--bright-blue);
  transition: all 0.2s ease-in-out;
  transform: translateX(0%);
  border-top-left-radius: 2px;
  border-top-right-radius: 2px;
}

[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(200%);
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="retro-tabs">
  <div ngTabList selectionMode="follow" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
  </div>

  <div ngTabPanel value="movie">
    <ng-template ngTabContent>Line 1</ng-template>
  </div>

  <div ngTabPanel value="theatres">
    <ng-template ngTabContent>Line 2</ng-template>
  </div>

  <div ngTabPanel value="showtimes">
    <ng-template ngTabContent>Line 3</ng-template>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: var(--bright-blue);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngTabs] {
  width: 600px;
}

[ngTabList] {
  gap: 1rem;
  display: flex;
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem;
  cursor: pointer;
  text-align: center;
  color: #000;
  background-color: #fff;
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngTab]:focus,
[ngTab]:hover {
  transform: translate(1px, 1px);
}

[ngTab]:focus {
  outline-offset: 2px;
  outline: 4px dashed var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}

[ngTab]:active,
[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngTabPanel] {
  flex: 1;
  font-size: 1.5rem;
  color: #000;
  background-color: #fff;
  padding: 1rem;
  margin-top: 1rem;
  min-height: 100px;
  display: grid;
  place-items: center;
  box-shadow: var(--retro-flat-shadow);
  background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
  background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
  background-size: 100% 4px;
}

[ngTabPanel]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--bright-blue);
}

[ngTabPanel][inert] {
  display: none;
}
```

## [Usage](#usage)

Tabs work well for organizing related content into distinct sections where users switch between different views or categories.

**Use tabs when:**

* Organizing related content into distinct sections
* Creating settings panels with multiple categories
* Building documentation with multiple topics
* Implementing dashboards with different views
* Showing content where users need to switch contexts

**Avoid tabs when:**

* Building sequential forms or wizards (use a stepper pattern)
* Navigating between pages (use router navigation)
* Showing single content sections (no need for tabs)
* Having more than 7-8 tabs (consider a different layout)

## [Features](#features)

* **Selection modes** - Tabs activate automatically on focus or require manual activation
* **Keyboard navigation** - Arrow keys, Home, and End for efficient tab navigation
* **Orientation** - Horizontal or vertical tab list layouts
* **Lazy content** - Tab panels render only when activated
* **Disabled tabs** - Disable individual tabs with focus management
* **Focus modes** - Roving tabindex or activedescendant focus strategies
* **RTL support** - Right-to-left language navigation

## [Examples](#examples)

### [Selection follows focus](#selection-follows-focus)

When selection follows focus, tabs activate immediately as you navigate with arrow keys. This provides instant feedback and works well for lightweight content.

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs>
  <div ngTabList selectionMode="follow" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 0.75rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab][aria-selected='false']:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
  border-bottom-right-radius: 0.5rem;
  border-bottom-left-radius: 0.5rem;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="material-tabs">
  <div ngTabList selectionMode="follow" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
    <div class="bottom-border"></div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab]:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--bright-blue);
}

.bottom-border {
  position: absolute;
  pointer-events: none;
  left: 0;
  bottom: 0;
  height: 3px;
  width: calc(100% / 3);
  background-color: var(--bright-blue);
  transition: all 0.2s ease-in-out;
  transform: translateX(0%);
  border-top-left-radius: 2px;
  border-top-right-radius: 2px;
}

[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(200%);
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="retro-tabs">
  <div ngTabList selectionMode="follow" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
  </div>

  <div ngTabPanel value="movie">
    <ng-template ngTabContent>Line 1</ng-template>
  </div>

  <div ngTabPanel value="theatres">
    <ng-template ngTabContent>Line 2</ng-template>
  </div>

  <div ngTabPanel value="showtimes">
    <ng-template ngTabContent>Line 3</ng-template>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: var(--bright-blue);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngTabs] {
  width: 600px;
}

[ngTabList] {
  gap: 1rem;
  display: flex;
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem;
  cursor: pointer;
  text-align: center;
  color: #000;
  background-color: #fff;
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngTab]:focus,
[ngTab]:hover {
  transform: translate(1px, 1px);
}

[ngTab]:focus {
  outline-offset: 2px;
  outline: 4px dashed var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}

[ngTab]:active,
[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngTabPanel] {
  flex: 1;
  font-size: 1.5rem;
  color: #000;
  background-color: #fff;
  padding: 1rem;
  margin-top: 1rem;
  min-height: 100px;
  display: grid;
  place-items: center;
  box-shadow: var(--retro-flat-shadow);
  background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
  background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
  background-size: 100% 4px;
}

[ngTabPanel]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--bright-blue);
}

[ngTabPanel][inert] {
  display: none;
}
```

Set `[selectionMode]="'follow'"` on the tab list to enable this behavior.

### [Manual activation](#manual-activation)

With manual activation, arrow keys move focus between tabs without changing the selected tab. Users press Space or Enter to activate the focused tab.

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs>
  <div ngTabList selectionMode="explicit" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 0.75rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab][aria-selected='false']:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
  border-bottom-right-radius: 0.5rem;
  border-bottom-left-radius: 0.5rem;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="material-tabs">
  <div ngTabList selectionMode="explicit" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
    <div class="bottom-border"></div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab]:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--bright-blue);
}

.bottom-border {
  position: absolute;
  pointer-events: none;
  left: 0;
  bottom: 0;
  height: 3px;
  width: calc(100% / 3);
  background-color: var(--bright-blue);
  transition: all 0.2s ease-in-out;
  transform: translateX(0%);
  border-top-left-radius: 2px;
  border-top-right-radius: 2px;
}

[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(200%);
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="retro-tabs">
  <div ngTabList selectionMode="explicit" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes">Reviews</div>
  </div>

  <div ngTabPanel value="movie">
    <ng-template ngTabContent>Line 1</ng-template>
  </div>

  <div ngTabPanel value="theatres">
    <ng-template ngTabContent>Line 2</ng-template>
  </div>

  <div ngTabPanel value="showtimes">
    <ng-template ngTabContent>Line 3</ng-template>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: var(--bright-blue);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast),
    0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
    0px -4px 0px 0px var(--quaternary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--quaternary-contrast), 0px 4px 0px 0px var(--quaternary-contrast),
    -4px 0px 0px 0px var(--quaternary-contrast), 0px -4px 0px 0px var(--quaternary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast),
    0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
    0px -4px 0px 0px var(--quaternary-contrast), 8px 8px 0px 0px var(--quaternary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--quaternary-contrast),
    0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast),
    0px -4px 0px 0px var(--quaternary-contrast), 0px 0px 0px 0px var(--quaternary-contrast);
}

[ngTabs] {
  width: 600px;
}

[ngTabList] {
  gap: 1rem;
  display: flex;
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem;
  cursor: pointer;
  text-align: center;
  color: #000;
  background-color: #fff;
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngTab]:focus,
[ngTab]:hover {
  transform: translate(1px, 1px);
}

[ngTab]:focus {
  outline-offset: 2px;
  outline: 4px dashed var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}

[ngTab]:active,
[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngTabPanel] {
  flex: 1;
  font-size: 1.5rem;
  color: #000;
  background-color: #fff;
  padding: 1rem;
  margin-top: 1rem;
  min-height: 100px;
  display: grid;
  place-items: center;
  box-shadow: var(--retro-flat-shadow);
  background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
  background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
  background-size: 100% 4px;
}

[ngTabPanel]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--bright-blue);
}

[ngTabPanel][inert] {
  display: none;
}
```

Use `[selectionMode]="'explicit'"` for heavy content panels to avoid unnecessary rendering.

### [Vertical tabs](#vertical-tabs)

Arrange tabs vertically for interfaces like settings panels or navigation sidebars.

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs>
  <div ngTabList orientation="vertical" selectedTab="movie">
    <div ngTab value="movie">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">videocam</span>
    </div>
    <div ngTab value="theatres">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true"
        >theater_comedy</span
      >
    </div>
    <div ngTab value="showtimes">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">reviews</span>
    </div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  height: 200px;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
  display: flex;
}

[ngTabList] {
  padding: 0;
  display: flex;
  flex-direction: column;
  list-style: none;
  position: relative;
  border-right: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  width: 80px;
  cursor: pointer;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
  display: grid;
  place-items: center;
}

[ngTab][aria-selected='false']:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  border-top-left-radius: 0.5rem;
  border-bottom-left-radius: 0.5rem;
}

.sliding-window {
  height: 300%;
  display: flex;
  flex-direction: column;
  transition: all 0.2s ease-in-out;
  flex: 1;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateY(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateY(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateY(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  flex: 1;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs>
  <div ngTabList class="material-tabs" orientation="vertical" selectedTab="movie">
    <div ngTab value="movie">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">videocam</span>
    </div>
    <div ngTab value="theatres">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true"
        >theater_comedy</span
      >
    </div>
    <div ngTab value="showtimes">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">reviews</span>
    </div>
    <div class="bottom-border"></div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  font-family: var(--inter-font);
  display: flex;;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  height: 200px;
  border-radius: 0.5rem;
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
  display: flex;
}

[ngTabList] {
  padding: 0;
  display: flex;
  flex-direction: column;
  list-style: none;
  position: relative;
  border-right: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  width: 80px;
  cursor: pointer;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
  display: grid;
  place-items: center;
}

[ngTab]:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--bright-blue);
}

.bottom-border {
  position: absolute;
  pointer-events: none;
  top: 0;
  right: 0;
  width: 3px;
  height: calc(100% / 3);
  background-color: var(--bright-blue);
  transition: all 0.2s ease-in-out;
  transform: translateY(0%);
  border-top-right-radius: 2px;
  border-bottom-right-radius: 2px;
}

[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
  transform: translateY(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
  transform: translateY(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
  transform: translateY(200%);
}

.sliding-window {
  height: 300%;
  display: flex;
  flex-direction: column;
  transition: all 0.2s ease-in-out;
  flex: 1;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateY(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateY(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateY(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  flex: 1;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="retro-tabs">
  <div ngTabList orientation="vertical" selectedTab="movie">
    <div ngTab value="movie">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">videocam</span>
    </div>
    <div ngTab value="theatres">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true"
        >theater_comedy</span
      >
    </div>
    <div ngTab value="showtimes">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">reviews</span>
    </div>
  </div>

  <div ngTabPanel [preserveContent]="true" value="movie">
    <ng-template ngTabContent>Line 1</ng-template>
  </div>

  <div ngTabPanel [preserveContent]="true" value="theatres">
    <ng-template ngTabContent>Line 2</ng-template>
  </div>

  <div ngTabPanel [preserveContent]="true" value="showtimes">
    <ng-template ngTabContent>Line 3</ng-template>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: var(--bright-blue);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngTabs] {
  width: 500px;
  display: flex;
}

[ngTabList] {
  gap: 1rem;
  display: flex;
  flex-direction: column;
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem 1.5rem;
  cursor: pointer;
  text-align: center;
  color: #000;
  background-color: #fff;
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngTab]:focus,
[ngTab]:hover {
  transform: translate(1px, 1px);
}

[ngTab]:focus {
  outline-offset: 2px;
  outline: 4px dashed var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}

[ngTab]:active,
[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngTabPanel] {
  flex: 1;
  font-size: 1.5rem;
  color: #000;
  background-color: #fff;
  padding: 1rem;
  margin-left: 1rem;
  min-width: 100px;
  display: grid;
  place-items: center;
  box-shadow: var(--retro-flat-shadow);
  background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
  background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
  background-size: 100% 4px;
}

[ngTabPanel]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--bright-blue);
}

[ngTabPanel][inert] {
  display: none;
}
```

Set `[orientation]="'vertical'"` on the tab list. Navigation changes to Up/Down arrow keys.

### [Lazy content rendering](#lazy-content-rendering)

Use the `ngTabContent` directive on an [`ng-template`](/api/core/ng-template) to defer rendering tab panels until they're shown.

```
<div ngTabs>
  <ul ngTabList [(selectedTab)]="selectedTab">
    <li ngTab value="tab1">Tab 1</li>
    <li ngTab value="tab2">Tab 2</li>
  </ul>

  <div ngTabPanel value="tab1">
    <ng-template ngTabContent>
      <!-- This content only renders when Tab 1 is shown -->
      <app-heavy-component />
    </ng-template>
  </div>

  <div ngTabPanel value="tab2">
    <ng-template ngTabContent>
      <!-- This content only renders when Tab 2 is shown -->
      <app-another-component />
    </ng-template>
  </div>
</div>
```

By default, content is removed from the DOM when the panel is hidden. Set `[preserveContent]="true"` to keep it, for example when the panel contains a video that shouldn't reset when switching tabs.

### [Disabled tabs](#disabled-tabs)

Disable specific tabs to prevent user interaction. Control whether disabled tabs can receive keyboard focus.

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs>
  <div ngTabList selectionMode="explicit" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes" disabled>Reviews</div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  border: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 10%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 0.75rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab][aria-disabled='true'] {
  cursor: default;
  color: color-mix(in srgb, var(--primary-contrast) 30%, transparent);
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab][aria-selected='false']:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab][aria-disabled='false'][aria-selected='false']:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  border-top-left-radius: 0.5rem;
  border-top-right-radius: 0.5rem;
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
  border-bottom-right-radius: 0.5rem;
  border-bottom-left-radius: 0.5rem;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="material-tabs">
  <div ngTabList selectionMode="explicit" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes" disabled>Reviews</div>
    <div class="bottom-border"></div>
  </div>

  <div class="sliding-window">
    <div ngTabPanel [preserveContent]="true" value="movie">
      <ng-template ngTabContent>Panel 1</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="theatres">
      <ng-template ngTabContent>Panel 2</ng-template>
    </div>

    <div ngTabPanel [preserveContent]="true" value="showtimes">
      <ng-template ngTabContent>Panel 3</ng-template>
    </div>
  </div>
</div>
```

### app.css

```
:host {
  font-family: var(--inter-font);
  display: flex;
  justify-content: center;
}

[ngTabs] {
  overflow: hidden;
  width: 600px;
  border-radius: 0.5rem;
  background-color: color-mix(in srgb, var(--bright-blue) 5%, transparent);
}

[ngTabList] {
  padding: 0;
  display: flex;
  list-style: none;
  position: relative;
  border-bottom: 1px solid color-mix(in srgb, var(--primary-contrast) 20%, transparent);
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem 0;
  cursor: pointer;
  text-align: center;
  color: color-mix(in srgb, var(--primary-contrast) 60%, transparent);
}

[ngTab][aria-disabled='true'] {
  cursor: default;
  color: color-mix(in srgb, var(--primary-contrast) 30%, transparent);
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab]:focus {
  outline-offset: -8px;
  border-radius: 0.7rem;
  outline: 2px solid var(--bright-blue);
}

[ngTab]:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

[ngTab][aria-selected='true'] {
  color: var(--bright-blue);
}

.bottom-border {
  position: absolute;
  pointer-events: none;
  left: 0;
  bottom: 0;
  height: 3px;
  width: calc(100% / 3);
  background-color: var(--bright-blue);
  transition: all 0.2s ease-in-out;
  transform: translateX(0%);
  border-top-left-radius: 2px;
  border-top-right-radius: 2px;
}

[ngTab]:nth-child(1)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(0%);
}
[ngTab]:nth-child(2)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(100%);
}
[ngTab]:nth-child(3)[aria-selected='true'] ~ .bottom-border {
  transform: translateX(200%);
}

.sliding-window {
  width: 300%;
  display: flex;
  transition: all 0.2s ease-in-out;
}

[ngTabList]:has([ngTab]:nth-child(1)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(0%);
}
[ngTabList]:has([ngTab]:nth-child(2)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-33.333%);
}
[ngTabList]:has([ngTab]:nth-child(3)[aria-selected='true']) ~ .sliding-window {
  transform: translateX(-66.666%);
}

[ngTabPanel] {
  display: grid;
  place-items: center;
  padding: 1rem;
  min-height: 100px;
  flex: 1;
}

[ngTabPanel]:focus {
  outline-offset: -4px;
  border-radius: 0.5rem;
  outline: 2px solid var(--bright-blue);
}
```

### app.ts

```
import {Component} from '@angular/core';
import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs';

@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrl: './app.css',
  imports: [TabList, Tab, Tabs, TabPanel, TabContent],
})
export class App {}
```

### app.html

```
<div ngTabs class="retro-tabs">
  <div ngTabList selectionMode="explicit" selectedTab="movie">
    <div ngTab value="movie">Movie</div>
    <div ngTab value="theatres">Cast</div>
    <div ngTab value="showtimes" disabled>Reviews</div>
  </div>

  <div ngTabPanel value="movie">
    <ng-template ngTabContent>Line 1</ng-template>
  </div>

  <div ngTabPanel value="theatres">
    <ng-template ngTabContent>Line 2</ng-template>
  </div>

  <div ngTabPanel value="showtimes">
    <ng-template ngTabContent>Line 3</ng-template>
  </div>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-size: 0.8rem;

  font-family: 'Press Start 2P';
  --retro-button-color: var(--bright-blue);
  --retro-shadow-light: color-mix(in srgb, #fff 20%, transparent);
  --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-clickable-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

[ngTabs] {
  width: 600px;
}

[ngTabList] {
  gap: 1rem;
  display: flex;
}

[ngTab] {
  flex: 1;
  outline: none;
  padding: 1rem;
  cursor: pointer;
  text-align: center;
  color: #000;
  background-color: #fff;
  box-shadow: var(--retro-clickable-shadow);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
}

[ngTab][aria-disabled='true'] {
  cursor: default;
  color: color-mix(in srgb, #000 30%, transparent);
}

[ngTab][aria-disabled='false'][aria-selected='false']:focus,
[ngTab][aria-disabled='false'][aria-selected='false']:hover {
  transform: translate(1px, 1px);
}

[ngTab]:focus {
  outline-offset: 2px;
  outline: 4px dashed var(--bright-blue);
}

[ngTab][aria-disabled='false'][aria-selected='false']:hover {
  background-color: color-mix(in srgb, var(--bright-blue) 50%, #fff);
}

[ngTab][aria-disabled='false']:active,
[ngTab][aria-selected='true'] {
  color: var(--page-background);
  background-color: var(--bright-blue);
  box-shadow: var(--retro-pressed-shadow);
  transform: translate(4px, 4px);
}

[ngTabPanel] {
  flex: 1;
  font-size: 1.5rem;
  color: #000;
  background-color: #fff;
  padding: 1rem;
  margin-top: 1rem;
  min-height: 100px;
  display: grid;
  place-items: center;
  box-shadow: var(--retro-flat-shadow);
  background: linear-gradient(rgba(0, 0, 0, 0.3) 50%, rgba(0, 0, 0, 0.1) 50%);
  background-color: color-mix(in srgb, var(--bright-blue) 10%, #fff);
  background-size: 100% 4px;
}

[ngTabPanel]:focus {
  outline-offset: 4px;
  outline: 4px dashed var(--bright-blue);
}

[ngTabPanel][inert] {
  display: none;
}
```

When `[softDisabled]="true"` on the tab list, disabled tabs can receive focus but cannot be activated. When `[softDisabled]="false"`, disabled tabs are skipped during keyboard navigation.

## [Testing](#testing)

Angular Aria provides component harnesses for testing tabs components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComponentHarness, HarnessLoader} from '@angular/cdk/testing';
import {TabsHarness} from '@angular/aria/tabs/testing';
import {MyTabsComponent} from './my-tabs'; // Your component

// A simple harness to help query content inside the tab panel
class TestContentHarness extends ComponentHarness {
  static hostSelector = '.test-content';
  async getText(): Promise<string> {
    return (await this.host()).text();
  }
}

describe('MyTabsComponent', () => {
  let fixture: ComponentFixture<MyTabsComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyTabsComponent],
    });

    fixture = TestBed.createComponent(MyTabsComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should switch tabs and scope panel queries', async () => {
    const tabs = await loader.getHarness(TabsHarness);

    // Get all tabs
    const tabItems = await tabs.getTabs();
    expect(tabItems.length).toBe(3);

    // Verify initial selection
    expect(await tabItems[0].isSelected()).toBe(true);
    expect(await tabItems[1].isSelected()).toBe(false);

    // Query content inside the active tab panel
    // TabHarness automatically scopes queries to its associated panel
    const content = await tabItems[0].getHarness(TestContentHarness);
    expect(await content.getText()).toBe('Content 1');

    // Switch to the second tab
    await tabItems[1].select();

    // Verify selection updated
    expect(await tabItems[0].isSelected()).toBe(false);
    expect(await tabItems[1].isSelected()).toBe(true);
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Tabs`](/api/aria/tabs/Tabs)
* [`TabList`](/api/aria/tabs/TabList)
* [`Tab`](/api/aria/tabs/Tab)
* [`TabPanel`](/api/aria/tabs/TabPanel)
* [`TabContent`](/api/aria/tabs/TabContent)

----
url: https://angular.dev/tools/devtools/router
----

The **Router Tree** tab lets you visualize the routing tree of your application. You can explore how routes are nested and view details about specific routes.

### [View route details](/tools/devtools/router#view-route-details)

When you select a specific route in the tree, Angular DevTools displays its properties in the sidebar on the right. This information includes:

* **Path**: The URL path for the route. If the route uses a custom URL matcher, DevTools displays the **Matcher** instead.
* **Component**: The component rendered for this route. If the route is a redirect, DevTools displays the **Redirect to** target instead.
* **Path Match**: The path matching strategy (`prefix` or `full`), if configured.
* **Data**: Static data associated with the route, displayed as a JSON tree.
* **Resolvers**: Route resolvers, displayed as key-value pairs.
* **Guards**: Any guards configured on the route, grouped by type — `canActivate`, `canActivateChild`, `canDeactivate`, and `canMatch`.
* **Providers**: Route-level providers, if configured.
* **Title**: The route title, if configured.
* **RunGuardsAndResolvers**: The re-run strategy for guards and resolvers, if configured.
* **Active**: Whether this route is currently active.
* **Auxiliary**: Indicates if the route is an auxiliary route (e.g., in a named outlet).
* **Lazy**: Indicates if the route is lazily loaded.

**NOTE:** Properties like Path Match, Data, Resolvers, Guards, Providers, Title, and RunGuardsAndResolvers only appear in the sidebar when they are configured on the selected route.

### [Navigate to a specific route](/tools/devtools/router#navigate-to-a-specific-route)

You can easily trigger navigation directly from the DevTools. While inspecting a route's details in the right sidebar, click on the **Navigate** icon next to the path string. This triggers the Angular router to navigate to that URL in your application.

----
url: https://angular.dev/api/animations/AnimationTransitionMetadata
----

```
    
      interface AnimationTransitionMetadata extends AnimationMetadata {}
    
    
```

An expression that describes a state change.

One or more animation objects to which this transition applies.

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/api/core/ApplicationModule
----

```
    
      class ApplicationModule {}
    
    
```

@paramappRef`ApplicationRef`

@returns`ApplicationModule`

Jump to details

----
url: https://angular.dev/api/router/OnSameUrlNavigation
----

```
    
      type OnSameUrlNavigation = 'reload' | 'ignore'
    
    
```

## [Description](#description)

How to handle a navigation request to the current URL. One of:

* `'ignore'` : The router ignores the request if it is the same as the current state.
* `'reload'` : The router processes the URL even if it is not different from the current state. One example of when you might want to use this option is if a `canMatch` guard depends on the application state and initially rejects navigation to a route. After fixing the state, you want to re-navigate to the same URL so that the route with the `canMatch` guard can activate.

Note that this only configures whether or not the Route reprocesses the URL and triggers related actions and events like redirects, guards, and resolvers. By default, the router re-uses a component instance when it re-navigates to the same component type without visiting a different component first. This behavior is configured by the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy). In order to reload routed components on same url navigation, you need to set `onSameUrlNavigation` to `'reload'` *and* provide a [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) which returns `false` for `shouldReuseRoute`. Additionally, resolvers and most guards for routes do not run unless the path or path params have changed (configured by `runGuardsAndResolvers`).

Jump to details

----
url: https://angular.dev/api/router/RouterState
----

```
    
      class RouterState extends Tree<ActivatedRoute> {}
    
    
```

@paramroot`TreeNode<ActivatedRoute>`

@paramsnapshot`RouterStateSnapshot`

The current snapshot of the router state

@returns`RouterState`

The current snapshot of the router state

@returns`string`

## [Usage Notes](#usage-notes)

Every node in the route tree is an [`ActivatedRoute`](/api/router/ActivatedRoute) instance that knows about the "consumed" URL segments, the extracted parameters, and the resolved data. Use the [`ActivatedRoute`](/api/router/ActivatedRoute) properties to traverse the tree from any node.

The following fragment shows how a component gets the root node of the current state to establish its own route tree:

```
@Component({templateUrl:'template.html'})
class MyComponent {
  constructor(router: Router) {
    const state: RouterState = router.routerState;
    const root: ActivatedRoute = state.root;
    const child = root.firstChild;
    const id: Observable<string> = child.params.map(p => p.id);
    //...
  }
}
```

Jump to details

----
url: https://angular.dev/reference/migrations/cleanup-unused-imports
----

As of version 19, Angular reports when a component's `imports` array contains symbols that aren't used in its template.

Running this schematic will clean up all unused imports within the project.

Run the schematic using the following command:

```
ng generate @angular/core:cleanup-unused-imports
```

#### [Before](#before)

```
import {Component} from '@angular/core';
import {UnusedDirective} from './unused';

@Component({
  template: 'Hello',
  imports: [UnusedDirective],
})
export class MyComp {}
```

#### [After](#after)

```
import {Component} from '@angular/core';

@Component({
  template: 'Hello',
  imports: [],
})
export class MyComp {}
```

----
url: https://angular.dev/api/animations/AnimationMetadataType
----

```
    
      enum AnimationMetadataType {}
    
    
```

Associates a named animation state with a set of CSS styles. See [`state()`](api/animations/state)

Data for a transition from one animation state to another. See [`transition()`](/api/animations/transition)

Contains a set of animation steps. See [`sequence()`](/api/animations/sequence)

Contains a set of animation steps. See `group()`

Contains an animation step. See [`animate()`](/api/animations/animate)

Contains a set of animation steps. See [`keyframes()`](/api/animations/keyframes)

Contains a set of CSS property-value pairs into a named style. See [`style()`](/api/animations/style)

Associates an animation with an entry trigger that can be attached to an element. See [`trigger()`](/api/animations/trigger)

Contains a re-usable animation. See [`animation()`](/api/animations/animation)

Contains data to use in executing child animations returned by a query. See [`animateChild()`](/api/animations/animateChild)

Contains animation parameters for a re-usable animation. See [`useAnimation()`](/api/animations/useAnimation)

Contains child-animation query data. See [`query()`](/api/animations/query)

Contains data for staggering an animation sequence. See [`stagger()`](/api/animations/stagger)

## [Description](#description)

Constants for the categories of parameters that can be defined for animations.

A corresponding function defines a set of parameters for each category, and collects them into a corresponding [`AnimationMetadata`](/api/animations/AnimationMetadata) object.

Jump to details

----
url: https://angular.dev/api/core/IterableDiffers
----

```
    
      class IterableDiffers {}
    
    
```

@paramfactories`IterableDifferFactory[]`

@returns`IterableDiffers`

@paramiterable`any`

@returns`IterableDifferFactory`

@paramfactories`IterableDifferFactory[]`

@paramparent`IterableDiffers | undefined`

@returns`IterableDiffers`

Takes an array of [`IterableDifferFactory`](/api/core/IterableDifferFactory) and returns a provider used to extend the inherited [`IterableDiffers`](/api/core/IterableDiffers) instance with the provided factories and return a new [`IterableDiffers`](/api/core/IterableDiffers) instance.

@paramfactories`IterableDifferFactory[]`

@returns`StaticProvider`

Usage notes

### [Example](#example)

The following example shows how to extend an existing list of factories, which will only be applied to the injector for this component and its children. This step is all that's required to make a new [`IterableDiffer`](/api/core/IterableDiffer) available.

```
@Component({
  viewProviders: [
    IterableDiffers.extend([new ImmutableListDiffer()])
  ]
})
```

Jump to details

----
url: https://angular.dev/errors/NG0913
----

### [Oversized images](#oversized-images)

When images are loaded, the **intrinsic size** of the downloaded file is checked against the actual size of the image on the page. The actual size is calculated using the **rendered size** of the image with CSS applied, multiplied by the [pixel device ratio](https://web.dev/codelab-density-descriptors/#pixel-density). If the downloaded image is much larger (more than 1200px too large in either dimension), this warning is triggered. Downloading oversized images can slow down page loading and have a negative effect on [Core Web Vitals](https://web.dev/vitals/).

### [Lazy-loaded LCP element](#lazy-loaded-lcp-element)

The largest contentful element on a page during load is considered the "LCP Element", which relates to [Largest Contentful Paint](https://web.dev/lcp/), one of the Core Web Vitals. Lazy loading an LCP element will have a strong negative effect on page performance. With this strategy, the browser has to complete layout calculations to determine whether the element is in the viewport before starting the image download. As a result, a warning is triggered when Angular detects that the LCP element has been given the `loading="lazy"` attribute.

### [Debugging the error](#debugging-the-error)

Use the image URL provided in the console warning to find the `<img>` element in question.

### [Ways to fix oversized images](#ways-to-fix-oversized-images)

* Use a smaller source image
* Add a [`srcset`](https://web.dev/learn/design/responsive-images/#responsive-images-with-srcset) if multiple sizes are needed for different layouts.
* Switch to use Angular's built-in image directive ([`NgOptimizedImage`](/api/common/NgOptimizedImage)), which generates [srcsets automatically](/guide/image-optimization#request-images-at-the-correct-size-with-automatic-srcset).

### [Ways to fix lazy-loaded LCP element](#ways-to-fix-lazy-loaded-lcp-element)

* Change the `loading` attribute to a different value such as `"eager"`.
* Switch to use Angular's built-in image directive ([`NgOptimizedImage`](/api/common/NgOptimizedImage)), which allows for easily [prioritizing LCP images](/guide/image-optimization#mark-images-as-priority).

### [Disabling Image Performance Warnings](#disabling-image-performance-warnings)

Both warnings can be disabled individually, site-wide, using a provider at the root of your application:

```
providers: [
  {
    provide: IMAGE_CONFIG,
    useValue: {
      disableImageSizeWarning: true,
      disableImageLazyLoadWarning: true
    }
  },
],
```

----
url: https://angular.dev/guide/i18n/format-data-locale
----

Angular provides the following built-in data transformation [pipes](guide/templates/pipes). The data transformation pipes use the [`LOCALE_ID`](api/core/LOCALE_ID "LOCALE_ID | Core - API | Angular") token to format data based on rules of each locale.

| Data transformation pipe                                                            | Details                                           |
| :---------------------------------------------------------------------------------- | :------------------------------------------------ |
| [`DatePipe`](api/common/DatePipe "DatePipe \| Common - API \| Angular")             | Formats a date value.                             |
| [`CurrencyPipe`](api/common/CurrencyPipe "CurrencyPipe \| Common - API \| Angular") | Transforms a number into a currency string.       |
| [`DecimalPipe`](api/common/DecimalPipe "DecimalPipe \| Common - API \| Angular")    | Transforms a number into a decimal number string. |
| [`PercentPipe`](api/common/PercentPipe "PercentPipe \| Common - API \| Angular")    | Transforms a number into a percentage string.     |

## [Use DatePipe to display the current date](#use-datepipe-to-display-the-current-date)

To display the current date in the format for the current locale, use the following format for the [`DatePipe`](/api/common/DatePipe).

```
{{ today | date }}
```

## [Override current locale for CurrencyPipe](#override-current-locale-for-currencypipe)

Add the `locale` parameter to the pipe to override the current value of [`LOCALE_ID`](/api/core/LOCALE_ID) token.

To force the currency to use American English (`en-US`), use the following format for the [`CurrencyPipe`](/api/common/CurrencyPipe)

```
{{ amount | currency: 'USD' : 'symbol' : '1.2-2' : 'en-US' }}
```

**HELPFUL:** The locale specified for the [`CurrencyPipe`](/api/common/CurrencyPipe) overrides the global [`LOCALE_ID`](/api/core/LOCALE_ID) token of your application.

## [What's next](#whats-next)

----
url: https://angular.dev/api/animations/AnimationTriggerMetadata
----

```
    
      interface AnimationTriggerMetadata extends AnimationMetadata {}
    
    
```

The trigger name, used to associate it with an element. Unique within the component.

An animation definition object, containing an array of state and transition declarations.

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/reference/configs/workspace-config
----

The `angular.json` file at the root level of an Angular workspace provides workspace-wide and project-specific configuration defaults. These are used for build and development tools provided by the Angular CLI. Path values given in the configuration are relative to the root workspace directory.

## [General JSON structure](#general-json-structure)

At the top-level of `angular.json`, a few properties configure the workspace and a `projects` section contains the remaining per-project configuration options. You can override Angular CLI defaults set at the workspace level through defaults set at the project level. You can also override defaults set at the project level using the command line.

The following properties, at the top-level of the file, configure the workspace.

| Properties       | Details                                                                                                                                                                                        |
| :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version`        | The configuration-file version.                                                                                                                                                                |
| `newProjectRoot` | Path where new projects are created through tools like `ng generate application` or `ng generate library`. Path can be absolute or relative to the workspace directory. Defaults to `projects` |
| `cli`            | A set of options that customize the [Angular CLI](tools/cli). See [Angular CLI configuration options](#angular-cli-configuration-options) below.                                               |
| `schematics`     | A set of [schematics](tools/cli/schematics) that customize the `ng generate` sub-command option defaults for this workspace. See [schematics](#schematics) below.                              |
| `projects`       | Contains a subsection for each application or library in the workspace, with project-specific configuration options.                                                                           |

The initial application that you create with `ng new app-name` is listed under "projects":

When you create a library project with `ng generate library`, the library project is also added to the `projects` section.

**HELPFUL:** The `projects` section of the configuration file does not correspond exactly to the workspace file structure.

* The initial application created by `ng new` is at the top level of the workspace file structure.
* Other applications and libraries are under the `projects` directory by default.

For more information, see [Workspace and project file structure](reference/configs/file-structure).

## [Angular CLI configuration options](#angular-cli-configuration-options)

The following properties are a set of options that customize the Angular CLI.

| Property               | Details                                                                                                                                                                    | Value type                            | Default value |
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------ | :------------ |
| `analytics`            | Share anonymous usage data with the Angular Team. A boolean value indicates whether or not to share data, while a UUID string shares data using a pseudonymous identifier. | `boolean` \| `string`                 | `false`       |
| `cache`                | Control [persistent disk cache](cli/cache) used by [Angular CLI Builders](tools/cli/cli-builder).                                                                          | [Cache options](#cache-options)       | `{}`          |
| `schematicCollections` | List schematics collections to use in `ng generate`.                                                                                                                       | `string[]`                            | `[]`          |
| `packageManager`       | The preferred package manager tool to use.                                                                                                                                 | `npm` \| `pnpm` \| `yarn` \| `bun`    | `npm`         |
| `warnings`             | Control Angular CLI specific console warnings.                                                                                                                             | [Warnings options](#warnings-options) | `{}`          |

### [Cache options](#cache-options)

| Property      | Details                                                                                                                                                                                                                            | Value type               | Default value    |
| :------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------- | :--------------- |
| `enabled`     | Configure whether disk caching is enabled for builds.                                                                                                                                                                              | `boolean`                | `true`           |
| `environment` | Configure in which environment disk cache is enabled. \_ `ci` enables caching only in continuous integration (CI) environments. \_ `local` enables caching only *outside* of CI environments. \* `all` enables caching everywhere. | `local` \| `ci` \| `all` | `local`          |
| `path`        | The directory used to stored cache results.                                                                                                                                                                                        | `string`                 | `.angular/cache` |

### [Warnings options](#warnings-options)

| Property          | Details                                                                         | Value type | Default value |
| :---------------- | :------------------------------------------------------------------------------ | :--------- | :------------ |
| `versionMismatch` | Show a warning when the global Angular CLI version is newer than the local one. | `boolean`  | `true`        |

## [Project configuration options](#project-configuration-options)

The following top-level configuration properties are available for each project, under `projects['project-name']`.

| Property      | Details                                                                                                                                                                                                               | Value type                                                      | Default value   |
| :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :-------------- |
| `root`        | The root directory for this project's files, relative to the workspace directory. Empty for the initial application, which resides at the top level of the workspace.                                                 | `string`                                                        | None (required) |
| `projectType` | One of "application" or "library" An application can run independently in a browser, while a library cannot.                                                                                                          | `application` \| `library`                                      | None (required) |
| `sourceRoot`  | The root directory for this project's source files.                                                                                                                                                                   | `string`                                                        | `''`            |
| `prefix`      | A string that Angular prepends to selectors when generating new components, directives, and pipes using `ng generate`. Can be customized to identify an application or feature area.                                  | `string`                                                        | `'app'`         |
| `i18n`        | Internationalization options for the project. Defines the source locale and additional locales to build. See [Define locales in the build configuration](guide/i18n/merge#define-locales-in-the-build-configuration). | See [i18n options](#i18n-options)                               | `{}`            |
| `schematics`  | A set of schematics that customize the `ng generate` sub-command option defaults for this project. See the [Generation schematics](#schematics) section.                                                              | See [schematics](#schematics)                                   | `{}`            |
| `architect`   | Configuration defaults for Architect builder targets for this project.                                                                                                                                                | See [Configuring builder targets](#configuring-builder-targets) | `{}`            |

## [i18n options](#i18n-options)

Use the `i18n` project option to define the application's source locale and any additional locales to build.

| Property       | Details                                                                                                                              | Value type                                              | Default value |
| :------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :------------ |
| `sourceLocale` | The locale used in the application source code. Can be a locale identifier string or a [configuration object](#sourcelocale-object). | `string` \| [sourceLocale object](#sourcelocale-object) | `"en-US"`     |
| `locales`      | A map of locale identifiers to translation files or [locale configuration objects](#locale-object).                                  | `object`                                                | `{}`          |

### [`sourceLocale` object](#sourcelocale-object)

Pass an object instead of a string to customize the output directory or base HREF for the source locale:

| Property   | Details                                                                                                                                       | Value type | Default value |
| :--------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :------------ |
| `code`     | The source locale identifier.                                                                                                                 | `string`   | `"en-US"`     |
| `baseHref` | Overrides the HTML `<base href>` for this locale. The output directory name stays as the locale code. Cannot be used together with `subPath`. | `string`   | Locale code   |
| `subPath`  | Sets both the output directory name and the HTML `<base href>` for this locale. Cannot be used together with `baseHref`.                      | `string`   | Locale code   |

### [Locale object](#locale-object)

Each `locales` entry can be a path string, an array of paths, or an object:

| Property      | Details                                                                                                                                             | Value type             | Default value |
| :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------- | :------------ |
| `translation` | Path or paths to the translation file(s) for this locale.                                                                                           | `string` \| `string[]` |               |
| `baseHref`    | Overrides the HTML `<base href>` for this locale. The output directory name stays as the locale identifier. Cannot be used together with `subPath`. | `string`               | Locale code   |
| `subPath`     | Sets both the output directory name and the HTML `<base href>` for this locale. Cannot be used together with `baseHref`.                            | `string`               | Locale code   |

**HELPFUL:** Use `subPath` rather than `baseHref` when you also need to rename the output directory — for example, to output to `de-DE/` instead of `de/`.

## [Schematics](#schematics)

[Angular schematics](tools/cli/schematics) are instructions for modifying a project by adding new files or modifying existing files. These can be configured by mapping the schematic name to a set of default options.

The "name" of a schematic is in the format: `<schematic-package>:<schematic-name>`. Schematics for the default Angular CLI `ng generate` sub-commands are collected in the package [`@schematics/angular`](https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/application/schema.json). For example, the schematic for generating a component with `ng generate component` is `@schematics/angular:component`.

The fields given in the schematic's schema correspond to the allowed command-line argument values and defaults for the Angular CLI sub-command options. You can update your workspace schema file to set a different default for a sub-command option. For example, to disable `standalone` in `ng generate component` by default:

```
{
  "projects": {
    "my-app": {
      "schematics": {
        "@schematics/angular:component": {
          "standalone": false
        }
      }
    }
  }
}
```

## [Configuring CLI builders](#configuring-cli-builders)

Architect is the tool that the Angular CLI uses to perform complex tasks, such as compilation and test running. Architect is a shell that runs a specified builder to perform a given task, according to a target configuration. You can define and configure new builders and targets to extend the Angular CLI. See [Angular CLI Builders](tools/cli/cli-builder).

### [Default Architect builders and targets](#default-architect-builders-and-targets)

Angular defines default builders for use with specific commands, or with the general `ng run` command. The JSON schemas that define the options and defaults for each of these builders are collected in the [`@angular/build`](https://github.com/angular/angular-cli/blob/main/packages/angular/build/builders.json) package. The schemas configure options for the following builders.

### [Configuring builder targets](#configuring-builder-targets)

The `architect` section of `angular.json` contains a set of Architect targets. Many of the targets correspond to the Angular CLI commands that run them. Other targets can be executed using the `ng run` command, and you can define your own targets.

Each target object specifies the `builder` for that target, which is the npm package for the tool that Architect runs. Each target also has an `options` section that configures default options for the target, and a `configurations` section that names and specifies alternative configurations for the target. See the example in [Build target](#build-target) below.

| Property       | Details                                                                                                                                                                                              |
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `build`        | Configures defaults for options of the `ng build` command. See the [Build target](#build-target) section for more information.                                                                       |
| `serve`        | Overrides build defaults and supplies extra serve defaults for the `ng serve` command. Besides the options available for the `ng build` command, it adds options related to serving the application. |
| `e2e`          | Overrides build defaults for building end-to-end testing applications using the `ng e2e` command.                                                                                                    |
| `test`         | Overrides build defaults for test builds and supplies extra test-running defaults for the `ng test` command.                                                                                         |
| `lint`         | Configures defaults for options of the `ng lint` command, which performs static code analysis on project source files.                                                                               |
| `extract-i18n` | Configures defaults for options of the `ng extract-i18n` command, which extracts localized message strings from source code and outputs translation files for internationalization.                  |

**HELPFUL:** All options in the configuration file must use `camelCase`, rather than `dash-case` as used on the command line.

## [Build target](#build-target)

Each target under `architect` has the following properties:

| Property         | Details                                                                                                                                                                                                                                                       |
| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `builder`        | The CLI builder used to create this target in the form of `<package-name>:<builder-name>`.                                                                                                                                                                    |
| `options`        | Build target default options.                                                                                                                                                                                                                                 |
| `configurations` | Alternative configurations for executing the target. Each configuration sets the default options for that intended environment, overriding the associated value under `options`. See [Alternate build configurations](#alternate-build-configurations) below. |

For example, to configure a build with optimizations disabled:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "optimization": false
          }
        }
      }
    }
  }
}
```

### [Alternate build configurations](#alternate-build-configurations)

Angular CLI comes with two build configurations: `production` and `development`. By default, the `ng build` command uses the `production` configuration, which applies several build optimizations, including:

* Bundling files
* Minimizing excess whitespace
* Removing comments and dead code
* Minifying code to use short, mangled names

You can define and name extra alternate configurations (such as `staging`, for instance) appropriate to your development process. You can select an alternate configuration by passing its name to the `--configuration` command line flag.

For example, to configure a build where optimization is enabled only for production builds (`ng build --configuration production`):

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "optimization": false
          },
          "configurations": {
            "production": {
              "optimization": true
            }
          }
        }
      }
    }
  }
}
```

You can also pass in more than one configuration name as a comma-separated list. For example, to apply both `staging` and `french` build configurations, use the command `ng build --configuration staging,french`. In this case, the command parses the named configurations from left to right. If multiple configurations change the same setting, the last-set value is the final one. In this example, if both `staging` and `french` configurations set the output path, the value in `french` would get used.

### [Extra build and test options](#extra-build-and-test-options)

The configurable options for a default or targeted build generally correspond to the options available for the [`ng build`](cli/build), and [`ng test`](cli/test) commands. For details of those options and their possible values, see the [Angular CLI Reference](cli).

| Options properties         | Details                                                                                                                                                                                                                                                                  |
| :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `assets`                   | An object containing paths to static assets to serve with the application. The default paths point to the project's `public` directory. See more in the [Assets configuration](#assets-configuration) section.                                                           |
| `styles`                   | An array of CSS files to add to the global context of the project. Angular CLI supports CSS imports and all major CSS preprocessors. See more in the [Styles and scripts configuration](#styles-and-scripts-configuration) section.                                      |
| `stylePreprocessorOptions` | An object containing option-value pairs to pass to style preprocessors. See more in the [Styles and scripts configuration](#styles-and-scripts-configuration) section.                                                                                                   |
| `inlineStyleLanguage`      | The stylesheet language to use for the application's inline component styles. Accepts `css`, `less`, `sass`, or `scss`, and defaults to `css`.                                                                                                                           |
| `scripts`                  | An object containing JavaScript files to add to the application. The scripts are loaded exactly as if you had added them in a `<script>` tag inside `index.html`. See more in the [Styles and scripts configuration](#styles-and-scripts-configuration) section.         |
| `budgets`                  | Default size-budget type and thresholds for all or parts of your application. You can configure the builder to report a warning or an error when the output reaches or exceeds a threshold size. See [Configure size budgets](tools/cli/build#configuring-size-budgets). |
| `fileReplacements`         | An object containing files and their compile-time replacements. See more in [Configure target-specific file replacements](tools/cli/environments#configure-environment-specific-defaults).                                                                               |
| `index`                    | A base HTML document which loads the application. See more in [Index configuration](#index-configuration).                                                                                                                                                               |
| `security`                 | An object containing the key `autoCsp` that can be set to `true` or `false`                                                                                                                                                                                              |

### [Extra serve options](#extra-serve-options)

The dev-server comes with its own set of options that generaly correspond to the options available for the [`ng serve`](cli/serve) command.

| Options properties | Details                                                                                                                                                                                                                   |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `allowedHosts`     | An array of host that the development server will respond to. This option sets the Vite option of the same name. For further details [see the vite docs](https://vite.dev/config/server-options.html#server-allowedhosts) |

## [Complex configuration values](#complex-configuration-values)

The `assets`, `index`, `outputPath`, `styles`, and `scripts` options can have either simple path string values, or object values with specific fields. The `sourceMap` and `optimization` options can be set to a simple boolean value. They can also be given a complex value using the configuration file.

The following sections provide more details of how these complex values are used in each case.

### [Assets configuration](#assets-configuration)

Each `build` target configuration can include an `assets` array that lists files or folders you want to copy as-is when building your project. By default, the contents of the `public/` directory are copied over.

To exclude an asset, you can remove it from the assets configuration.

You can further configure assets to be copied by specifying assets as objects, rather than as simple paths relative to the workspace root. An asset specification object can have the following fields.

| Fields                       | Details                                                                                                                                   |
| :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `glob`                       | A [node-glob](https://github.com/isaacs/node-glob/blob/main/README.md) using `input` as base directory.                                   |
| `input`                      | A path relative to the workspace root.                                                                                                    |
| [`output`](/api/core/output) | A path relative to `outDir`. Because of the security implications, the Angular CLI never writes files outside of the project output path. |
| `ignore`                     | A list of globs to exclude.                                                                                                               |
| `followSymlinks`             | Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched. Defaults to `false`.         |

For example, the following asset objects copy the contents of `src/assets/` to `/assets/` and `src/favicon.ico` to the output root.

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets/",
                "output": "/assets/"
              },
              {
                "glob": "favicon.ico",
                "input": "src/",
                "output": "/"
              }
            ]
          }
        }
      }
    }
  }
}
```

The following example uses the `ignore` field to exclude certain files in the assets directory from being copied into the build:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets/",
                "ignore": ["**/*.svg"],
                "output": "/assets/"
              }
            ]
          }
        }
      }
    }
  }
}
```

### [Styles and scripts configuration](#styles-and-scripts-configuration)

An array entry for the `styles` and `scripts` options can be a simple path string, or an object that points to an extra entry-point file. The associated builder loads that file and its dependencies as a separate bundle during the build. With a configuration object, you have the option of naming the bundle for the entry point, using a `bundleName` field.

The bundle is injected by default, but you can set `inject` to `false` to exclude the bundle from injection. For example, the following object values create and name a bundle that contains styles and scripts, and excludes it from injection:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "styles": [
              {
                "input": "src/external-module/styles.scss",
                "inject": false,
                "bundleName": "external-module"
              }
            ],
            "scripts": [
              {
                "input": "src/external-module/main.js",
                "inject": false,
                "bundleName": "external-module"
              }
            ]
          }
        }
      }
    }
  }
}
```

#### [Style preprocessor options](#style-preprocessor-options)

In Sass, you can make use of the `includePaths` feature for both component and global styles. This allows you to add extra base paths that are checked for imports.

To add paths, use the `stylePreprocessorOptions` option:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "stylePreprocessorOptions": {
              "includePaths": ["src/style-paths"]
            }
          }
        }
      }
    }
  }
}
```

Files in that directory, such as `src/style-paths/_variables.scss`, can be imported from anywhere in your project without the need for a relative path:

### src/app/app.scss

```
// A relative path works
@import '../style-paths/variables';

// But now this works as well
@import 'variables';
```

**HELPFUL:** You also need to add any styles or scripts to the `test` builder if you need them for unit tests. See also [Using runtime-global libraries inside your application](tools/libraries/using-libraries#using-runtime-global-libraries-inside-your-app).

### [Optimization configuration](#optimization-configuration)

The `optimization` option can be either a boolean or an object for more fine-tune configuration. This option enables various optimizations of the build output, including:

* Minification of scripts and styles
* Tree-shaking
* Dead-code elimination
* [Inlining of critical CSS](/tools/cli/build#critical-css-inlining)
* Fonts inlining

Several options can be used to fine-tune the optimization of an application.

| Options   | Details                                                        | Value type                                                               | Default value |
| :-------- | :------------------------------------------------------------- | :----------------------------------------------------------------------- | :------------ |
| `scripts` | Enables optimization of the scripts output.                    | `boolean`                                                                | `true`        |
| `styles`  | Enables optimization of the styles output.                     | `boolean` \| [Styles optimization options](#styles-optimization-options) | `true`        |
| `fonts`   | Enables optimization for fonts. This requires internet access. | `boolean` \| [Fonts optimization options](#fonts-optimization-options)   | `true`        |

#### [Styles optimization options](#styles-optimization-options)

| Options                 | Details                                                                                                                  | Value type | Default value |
| :---------------------- | :----------------------------------------------------------------------------------------------------------------------- | :--------- | :------------ |
| `minify`                | Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers, and minimizing values.       | `boolean`  | `true`        |
| `inlineCritical`        | Extract and inline critical CSS definitions to improve [First Contentful Paint](https://web.dev/first-contentful-paint). | `boolean`  | `true`        |
| `removeSpecialComments` | Remove comments in global CSS that contains `@license` or `@preserve` or that starts with `//!` or `/*!`.                | `boolean`  | `true`        |

#### [Fonts optimization options](#fonts-optimization-options)

| Options  | Details                                                                                                                                                                                                             | Value type | Default value |
| :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------- | :------------ |
| `inline` | Reduce [render blocking requests](https://web.dev/render-blocking-resources) by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This requires internet access. | `boolean`  | `true`        |

You can supply a value such as the following to apply optimization to one or the other:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "optimization": {
              "scripts": true,
              "styles": {
                "minify": true,
                "inlineCritical": true
              },
              "fonts": true
            }
          }
        }
      }
    }
  }
}
```

### [Source map configuration](#source-map-configuration)

The `sourceMap` builder option can be either a boolean or an object for more fine-tune configuration to control the source maps of an application.

| Options          | Details                                                      | Value type | Default value |
| :--------------- | :----------------------------------------------------------- | :--------- | :------------ |
| `scripts`        | Output source maps for all scripts.                          | `boolean`  | `true`        |
| `styles`         | Output source maps for all styles.                           | `boolean`  | `true`        |
| `vendor`         | Resolve vendor packages source maps.                         | `boolean`  | `false`       |
| `hidden`         | Omit link to sourcemaps from the output JavaScript.          | `boolean`  | `false`       |
| `sourcesContent` | Output original source content for files within source maps. | `boolean`  | `true`        |

The example below shows how to toggle one or more values to configure the source map outputs:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "sourceMap": {
              "scripts": true,
              "styles": false,
              "hidden": true,
              "vendor": true
            }
          }
        }
      }
    }
  }
}
```

**HELPFUL:** When using hidden source maps, source maps are not referenced in the bundle. These are useful if you only want source maps to map stack traces in error reporting tools without showing up in browser developer tools. Note that even though `hidden` prevents the source map from being linked in the output bundle, your deployment process must take care not to serve the generated sourcemaps in production, or else the information is still leaked.

#### [Source maps without sources content](#source-maps-without-sources-content)

You can generate source maps without the `sourcesContent` field, which contains the original source code. This allows you to deploy source maps to production for better error reporting with original source names while protecting your source code from exposure.

To exclude sources content from source maps, set the `sourcesContent` option to `false`:

```
{
  "projects": {
    "my-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "sourceMap": {
              "scripts": true,
              "styles": true,
              "sourcesContent": false
            }
          }
        }
      }
    }
  }
}
```

### [Index configuration](#index-configuration)

Configures generation of the application's HTML index.

The `index` option can be either a string or an object for more fine-tune configuration.

When supplying the value as a string the filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path.

#### [Index options](#index-options)

| Options                      | Details                                                                                                                                                                          | Value type | Default value   |
| :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :-------------- |
| `input`                      | The path of a file to use for the application's generated HTML index.                                                                                                            | `string`   | None (required) |
| [`output`](/api/core/output) | The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path. | `string`   | `index.html`    |

### [Output path configuration](#output-path-configuration)

The `outputPath` option can be either a String which will be used as the `base` value or an Object for more fine-tune configuration.

Several options can be used to fine-tune the output structure of an application.

| Options   | Details                                                                                                                                                           | Value type | Default value |
| :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------- | :------------ |
| `base`    | Specify the output path relative to workspace root.                                                                                                               | `string`   |               |
| `browser` | The output directory name for your browser build is within the base output path. This can be safely served to users.                                              | `string`   | `browser`     |
| `server`  | The output directory name of your server build within the output path base.                                                                                       | `string`   | `server`      |
| `media`   | The output directory name for your media files located within the output browser directory. These media files are commonly referred to as resources in CSS files. | `string`   | `media`       |

----
url: https://angular.dev/api/aria/tree/Tree
----

```
    
      class Tree<V> implements OnDestroy {}
    
    
```

A reference to the host element.

A unique identifier for the tree.

Orientation of the tree.

Whether multi-selection is allowed.

Whether the tree is disabled.

The selection strategy used by the tree.

* `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar).
* `follow`: The focused item is automatically selected.

The focus strategy used by the tree.

* `roving`: Focus is moved to the active item using `tabindex`.
* `activedescendant`: Focus remains on the tree container, and `aria-activedescendant` is used to indicate the active item.

Whether navigation wraps.

Whether to allow disabled items to receive focus. When `true`, disabled items are focusable but not interactive. When `false`, disabled items are skipped during navigation.

The delay in seconds before the typeahead search is reset.

The tabindex of the tree.

The values of the currently selected items.

Text direction.

The `aria-current` type. It can be used in navigation trees to indicate the currently active item. See <https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current> for more details.

The ID of the active descendant in the tree.

@returns`void`

@paramoptions`ScrollIntoViewOptions`

@returns`void`

## [Description](#description)

A container that transforms nested lists into an accessible, ARIA-compliant tree structure. It manages the overall state of the tree, including selection, expansion, and keyboard navigation.

```
<ul ngTree [(value)]="selectedItems" [multi]="true">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: treeData, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.name) {
    <li ngTreeItem [parent]="parent" [value]="node.name" [label]="node.name">
      {{ node.name }}
      @if (node.children) {
        <ul role="group">
          <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
            <ng-template
              [ngTemplateOutlet]="treeNodes"
              [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
            />
          </ng-template>
        </ul>
      }
    </li>
  }
</ng-template>
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/moveItemInArray
----

```
function moveItemInArray<T = any>(
  array: T[],
  fromIndex: number,
  toIndex: number,
): void;
```

@paramarray`T[]`

Array in which to move the item.

@paramfromIndex`number`

Starting index of the item.

@paramtoIndex`number`

Index to which the item should be moved.

@returns`void`

Jump to details

----
url: https://angular.dev/api/forms/FormBuilder
----

```
    
      class FormBuilder {
}
    
    
```

Returns a FormBuilder in which automatically constructed [`FormControl`](/api/forms/FormControl) elements have `{nonNullable: true}` and are non-nullable.

**Constructing non-nullable controls**

When constructing a control, it will be non-nullable, and will reset to its initial value.

```
let nnfb = new FormBuilder().nonNullable;
let name = nnfb.control('Alex'); // FormControl<string>
name.reset();
console.log(name); // 'Alex'
```

**Constructing non-nullable groups or arrays**

When constructing a group or array, all automatically created inner controls will be non-nullable, and will reset to their initial values.

```
let nnfb = new FormBuilder().nonNullable;
let name = nnfb.group({who: 'Alex'}); // FormGroup<{who: FormControl<string>}>
name.reset();
console.log(name); // {who: 'Alex'}
```

**Constructing *nullable* fields on groups or arrays**

It is still possible to have a nullable field. In particular, any [`FormControl`](/api/forms/FormControl) which is *already* constructed will not be altered. For example:

```
let nnfb = new FormBuilder().nonNullable;
// FormGroup<{who: FormControl<string|null>}>
let name = nnfb.group({who: new FormControl('Alex')});
name.reset(); console.log(name); // {who: null}
```

Because the inner control is constructed explicitly by the caller, the builder has no control over how it is created, and cannot exclude the `null`.

Constructs a new [`FormGroup`](/api/forms/FormGroup) instance. Accepts a single generic argument, which is an object containing all the keys and corresponding inner control types.

@paramcontrols`T`

A collection of child controls. The key for each child is the name under which it is registered. Each value can be one of:

* An [`AbstractControl`](/api/forms/AbstractControl) instance — passed through as-is.
* A raw value — shorthand for `fb.control(value)`.
* A [`FormControlState`](/api/forms/FormControlState) object (`{value, disabled}`) — initializes the control with a value and disabled status.
* A [`ControlConfig`](/api/forms/ControlConfig) tuple — `[value | FormControlState, validatorOrOpts?, asyncValidator?]`. The second element accepts a [`ValidatorFn`](/api/forms/ValidatorFn), an array of [`ValidatorFn`](/api/forms/ValidatorFn), or an [`AbstractControlOptions`](/api/forms/AbstractControlOptions) object (use this to set `updateOn`). The optional third element accepts an [`AsyncValidatorFn`](/api/forms/AsyncValidatorFn) or an array of [`AsyncValidatorFn`](/api/forms/AsyncValidatorFn).

@paramoptions`AbstractControlOptions | null | undefined`

Configuration options object for the [`FormGroup`](/api/forms/FormGroup). The object should have the [`AbstractControlOptions`](/api/forms/AbstractControlOptions) type and might contain the following fields:

* `validators`: A synchronous validator function, or an array of validator functions.
* `asyncValidators`: A single async validator or array of async validator functions.
* `updateOn`: The event upon which the control should be updated (options: 'change' | 'blur' | submit').

@returns`FormGroup<ɵNullableFormControls<T>>`

Usage notes

### [Different ways to specify a control value](#different-ways-to-specify-a-control-value)

```
const fb = new FormBuilder();

fb.group({
  // Raw value — equivalent to fb.control('Ada')
  name: 'Ada',

  // FormControlState — sets initial value and disabled status
  city: {value: 'London', disabled: true},

  // ControlConfig tuple — value with a sync validator
  email: ['ada@example.com', Validators.email],

  // ControlConfig tuple — value, sync validator, and async validator
  username: ['ada', Validators.required, checkUsernameAvailable],

  // ControlConfig tuple — disabled value with updateOn option
  role: [{value: 'admin', disabled: true}, {updateOn: 'blur'}],

  // Pre-built control — passed through unchanged
  address: new FormControl('', Validators.required),
});
```

Constructs a new [`FormGroup`](/api/forms/FormGroup) instance.

@deprecated

This API is not typesafe and can result in issues with Closure Compiler renaming. Use the [`FormBuilder#group`](/api/forms/FormBuilder#group) overload with [`AbstractControlOptions`](/api/forms/AbstractControlOptions) instead. Note that [`AbstractControlOptions`](/api/forms/AbstractControlOptions) expects `validators` and `asyncValidators` to be valid validators. If you have custom validators, make sure their validation function parameter is [`AbstractControl`](/api/forms/AbstractControl) and not a sub-class, such as [`FormGroup`](/api/forms/FormGroup). These functions will be called with an object of type [`AbstractControl`](/api/forms/AbstractControl) and that cannot be automatically downcast to a subclass, so TypeScript sees this as an error. For example, change the `(group: FormGroup) => ValidationErrors|null` signature to be `(group: AbstractControl) => ValidationErrors|null`.

@paramcontrols`{ [key: string]: any; }`

A record of child controls. The key for each child is the name under which the control is registered.

@paramoptions`{ [key: string]: any; }`

Configuration options object for the [`FormGroup`](/api/forms/FormGroup). The legacy configuration object consists of:

* `validator`: A synchronous validator function, or an array of validator functions.
* `asyncValidator`: A single async validator or array of async validator functions Note: the legacy format is deprecated and might be removed in one of the next major versions of Angular.

@returns`FormGroup<any>`

@deprecated

Use `nonNullable` instead.

@paramformState`T | FormControlState<T>`

@paramopts`FormControlOptions & { initialValueIsDefault: true; }`

@returns`FormControl<T>`

@paramformState`T | FormControlState<T>`

@paramopts`FormControlOptions & { nonNullable: true; }`

@returns`FormControl<T>`

@deprecated

When passing an `options` argument, the `asyncValidator` argument has no effect.

@paramformState`T | FormControlState<T>`

@paramopts`FormControlOptions`

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[]`

@returns`FormControl<T | null>`

@paramformState`T | FormControlState<T>`

@paramvalidatorOrOpts`ValidatorFn | FormControlOptions | ValidatorFn[] | null | undefined`

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

@returns`FormControl<T | null>`

Constructs a new [`FormArray`](/api/forms/FormArray) from the given array of configurations, validators and options. Accepts a single generic argument, which is the type of each control inside the array.

@paramcontrols`T[]`

An array of child controls or control configs. Each child control is given an index when it is registered.

@paramvalidatorOrOpts`ValidatorFn | AbstractControlOptions | ValidatorFn[] | null | undefined`

A synchronous validator function, or an array of such functions, or an [`AbstractControlOptions`](/api/forms/AbstractControlOptions) object that contains validation functions and a validation trigger.

@paramasyncValidator`AsyncValidatorFn | AsyncValidatorFn[] | null | undefined`

A single async validator or array of async validator functions.

@returns`FormArray<ɵElement<T, null>>`

## [Description](#description)

Creates an [`AbstractControl`](/api/forms/AbstractControl) from a user-specified configuration.

The [`FormBuilder`](/api/forms/FormBuilder) provides syntactic sugar that shortens creating instances of a [`FormControl`](/api/forms/FormControl), [`FormGroup`](/api/forms/FormGroup), or [`FormArray`](/api/forms/FormArray). It reduces the amount of boilerplate needed to build complex forms.

Jump to details

----
url: https://angular.dev/api/forms/NgModelGroup
----

## [API](/api/forms/NgModelGroup#api)

```
    
      class NgModelGroup extends AbstractFormGroupDirective implements OnInit ,OnDestroy {}
    
    
```

@paramparent`ControlContainer`

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@returns`NgModelGroup`

Tracks the name of the [`NgModelGroup`](/api/forms/NgModelGroup) bound to the directive. The name corresponds to a key in the parent [`NgForm`](/api/forms/NgForm).

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](/api/forms/NgModelGroup#description)

Creates and binds a [`FormGroup`](/api/forms/FormGroup) instance to a DOM element.

This directive can only be used as a child of [`NgForm`](/api/forms/NgForm) (within `<form>` tags).

Use this directive to validate a sub-group of your form separately from the rest of your form, or if some values in your domain model make more sense to consume together in a nested object.

Provide a name for the sub-group and it will become the key for the sub-group in the form's full value. If you need direct access, export the directive into a local template variable using `ngModelGroup` (ex: `#myGroup="ngModelGroup"`).

## [Exported by](/api/forms/NgModelGroup#exported-by)

* `FormsModule`

## [Usage Notes](/api/forms/NgModelGroup#usage-notes)

### [Consuming controls in a grouping](/api/forms/NgModelGroup#consuming-controls-in-a-grouping)

The following example shows you how to combine controls together in a sub-group of the form.

```
import {Component} from '@angular/core';
import {NgForm} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form #f="ngForm" (ngSubmit)="onSubmit(f)">
      @if (nameCtrl.invalid) {
        <p>Name is invalid.</p>
      }
      <div ngModelGroup="name" #nameCtrl="ngModelGroup">
        <input name="first" [ngModel]="name.first" minlength="2" />
        <input name="middle" [ngModel]="name.middle" maxlength="2" />
        <input name="last" [ngModel]="name.last" required />
      </div>

      <input name="email" ngModel />
      <button>Submit</button>
    </form>

    <button (click)="setValue()">Set value</button>
  `,
  standalone: false,
})
export class NgModelGroupComp {
  name = {first: 'Nancy', middle: 'J', last: 'Drew'};

  onSubmit(f: NgForm) {
    console.log(f.value); // {name: {first: 'Nancy', middle: 'J', last: 'Drew'}, email: ''}
    console.log(f.valid); // true
  }

  setValue() {
    this.name = {first: 'Bess', middle: 'S', last: 'Marvin'};
  }
}
```

Jump to details

----
url: https://angular.dev/api/common/DOCUMENT
----

```
    
      const DOCUMENT: InjectionToken<Document>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ChangeDetectorRef
----

```
    
      abstract class ChangeDetectorRef {}
    
    
```

When a view uses the [`ChangeDetectionStrategy#OnPush`](/api/core/ChangeDetectionStrategy#OnPush) (checkOnce) change detection strategy, explicitly marks the view as changed so that it can be checked again.

Components are normally marked as dirty (in need of rerendering) when inputs have changed or events have fired in the view. Call this method to ensure that a component is checked even if these triggers have not occurred.

@returns`void`

Detaches this view from the change-detection tree. A detached view is not checked until it is reattached. Use in combination with `detectChanges()` to implement local change detection checks.

Detached views are not checked during change detection runs until they are re-attached, even if they are marked as dirty.

@returns`void`

Checks this view and its children. Use in combination with [`ChangeDetectorRef#detach`](/api/core/ChangeDetectorRef#detach) to implement local change detection checks.

@returns`void`

Re-attaches the previously detached view to the change detection tree. Views are attached to the tree by default.

@returns`void`

## [Usage Notes](#usage-notes)

The following examples demonstrate how to modify default change-detection behavior to perform explicit detection when needed.

### [Use `markForCheck()` with `CheckOnce` strategy](#use-markforcheck-with-checkonce-strategy)

The following example sets the `OnPush` change-detection strategy for a component (`CheckOnce`, rather than the default `CheckAlways`), then forces a second check after an interval.

```
@Component({
  selector: 'app-root',
  template: `Number of ticks: {{ numberOfTicks }}`,
})
class AppComponent {
  numberOfTicks = 0;

  constructor(private ref: ChangeDetectorRef) {
    setInterval(() => {
      this.numberOfTicks++;
      // require view to be updated
      this.ref.markForCheck();
    }, 1000);
  }
}
```

### [Detach change detector to limit how often check occurs](#detach-change-detector-to-limit-how-often-check-occurs)

The following example defines a component with a large list of read-only data that is expected to change constantly, many times per second. To improve performance, we want to check and update the list less often than the changes actually occur. To do that, we detach the component's change detector and perform an explicit local check every five seconds.

```
class DataListProvider {
  // in a real application the returned data will be different every time
  get data() {
    return [1, 2, 3, 4, 5];
  }
}

@Component({
  selector: 'giant-list',
  template: ` <ul>
    @for (d of dataProvider.data; track $index) {
      <li>Item {{ d }}</li>
    }
  </ul>`,
})
class GiantList {
  constructor(
    private ref: ChangeDetectorRef,
    public dataProvider: DataListProvider,
  ) {
    ref.detach();
    setInterval(() => {
      this.ref.detectChanges();
    }, 5000);
  }
}

@Component({
  selector: 'app',
  providers: [DataListProvider],
  imports: [GiantList],
  template: `<giant-list />`,
})
class App {}
```

### [Reattaching a detached component](#reattaching-a-detached-component)

The following example creates a component displaying live data. The component detaches its change detector from the main change detector tree when the `live` property is set to false, and reattaches it when the property becomes true.

```
class DataProvider {
  data = signal(1);
  constructor() {
    setInterval(() => {
      this.data.set(2);
    }, 500);
  }
}

@Component({
  selector: 'live-data',
  template: 'Data: {{dataProvider.data()}}',
})
class LiveData {
  live = input.required<boolean>();
  constructor(
    private ref: ChangeDetectorRef,
    public dataProvider: DataProvider,
  ) {
    effect(() => {
      if (this.live()) {
        this.ref.reattach();
      } else {
        this.ref.detach();
      }
    });
  }
}

@Component({
  selector: 'app',
  providers: [DataProvider],
  imports: [FormsModule, LiveData],
  template: `
    Live Update: <input type="checkbox" [(ngModel)]="live" />
    <live-data [live]="live"></live-data>
  `,
})
class App1 {
  live = true;
}
```

Jump to details

----
url: https://angular.dev/cli/generate/directive
----

```
ng generate directive [name] [options]
```

```
ng generate d [name] [options]
```

Creates a new directive in your project. Directives are used to extend the behavior or appearance of HTML elements and components. They allow you to manipulate the DOM, add custom attributes, and respond to events. This schematic generates the necessary files and boilerplate code for a new directive.

## [Arguments](/cli/generate/directive#arguments)

`name`

The name for the new directive. This will be used to create the directive's class and spec files (e.g., `my-directive.directive.ts` and `my-directive.directive.spec.ts`).

Value Type`string`

## [Options](/cli/generate/directive#options)

`add-type-to-class-name`

When true, the 'type' option will be appended to the generated class name. When false, only the file name will include the type.

Value Type`boolean`Default`true`

`export`

Automatically export the directive from the specified NgModule, making it accessible to other modules in the application.

Value Type`boolean`Default`false`

`flat`

Creates the new directive files at the top level of the current project. If set to false, a new folder with the directive's name will be created to contain the files.

Value Type`boolean`Default`true`

`module`

Alias`m`

Specify the NgModule where the directive should be declared. If not provided, the CLI will attempt to find the closest NgModule in the directive's path.

Value Type`string`

`prefix`

Alias`p`

A prefix to be added to the directive's selector. For example, if the prefix is `app` and the directive name is `highlight`, the selector will be `appHighlight`.

Value Type`string`

`project`

The name of the project where the directive should be added. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`selector`

The HTML selector to use for this directive. If not provided, a selector will be generated based on the directive's name (e.g., `appHighlight`).

Value Type`string`

`skip-import`

Do not automatically import the new directive into its closest NgModule.

Value Type`boolean`Default`false`

`skip-tests`

Skip the generation of a unit test file `spec.ts` for the new directive.

Value Type`boolean`Default`false`

`standalone`

Generate a standalone directive. Standalone directives are self-contained and don't need to be declared in an NgModule. They can be used independently or imported directly into other standalone components or directives.

Value Type`boolean`Default`true`

`type`

Append a custom type to the directive's filename. For example, if you set the type to `directive`, the file will be named `example.directive.ts`.

Value Type`string`

Jump to details

----
url: https://angular.dev/api/animations/browser/testing/MockAnimationDriver
----

```
    
      class MockAnimationDriver implements AnimationDriver {}
    
    
```

@paramprop`string`

@returns`boolean`

@paramprop`string`

@returns`boolean`

@paramelm1`any`

@paramelm2`any`

@returns`boolean`

@paramkeyframes`ɵStyleDataMap[]`

@paramduration`number`

@paramdelay`number`

@parameasing`string`

@parampreviousPlayers`any[]`

@returns`MockAnimationPlayer`

Jump to details

----
url: https://angular.dev/api/service-worker/SwPush
----

```
    
      class SwPush {}
    
    
```

@paramsw`NgswCommChannel`

@returns`SwPush`

Emits the payloads of the received push notification messages.

Emits the payloads of the received push notification messages as well as the action the user interacted with. If no action was used the `action` property contains an empty string `''`.

Note that the `notification` property does **not** contain a [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification) object but rather a [NotificationOptions](https://notifications.spec.whatwg.org/#dictdef-notificationoptions) object that also includes the `title` of the [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification) object.

Emits the payloads of notifications that were closed, along with the action (if any) associated with the close event. If no action was used, the `action` property contains an empty string `''`.

Note that the `notification` property does **not** contain a [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification) object but rather a [NotificationOptions](https://notifications.spec.whatwg.org/#dictdef-notificationoptions) object that also includes the `title` of the [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification) object.

Emits updates to the push subscription, including both the previous (`oldSubscription`) and current (`newSubscription`) values. Either subscription may be `null`, depending on the context:

* `oldSubscription` is `null` if no previous subscription existed.
* `newSubscription` is `null` if the subscription was invalidated and not replaced.

This stream allows clients to react to automatic changes in push subscriptions, such as those triggered by browser expiration or key rotation.

Emits the currently active [PushSubscription](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) associated to the Service Worker registration or `null` if there is no subscription.

True if the Service Worker is enabled (supported by the browser and enabled via [`ServiceWorkerModule`](/api/service-worker/ServiceWorkerModule)).

Subscribes to Web Push Notifications, after requesting and receiving user permission.

@paramoptions`{ serverPublicKey: string; }`

An object containing the `serverPublicKey` string.

@returns`Promise<PushSubscription>`

A Promise that resolves to the new subscription object.

Unsubscribes from Service Worker push notifications.

@returns`Promise<void>`

A Promise that is resolved when the operation succeeds, or is rejected if there is no active subscription or the unsubscribe operation fails.

## [Usage Notes](#usage-notes)

You can inject a [`SwPush`](/api/service-worker/SwPush) instance into any component or service as a dependency.

```
import {SwPush} from '@angular/service-worker';
export class AppComponent {
  constructor(readonly swPush: SwPush) {}
}
```

To subscribe, call [`SwPush.requestSubscription()`](/api/service-worker/SwPush#requestSubscription), which asks the user for permission. The call returns a `Promise` with a new [`PushSubscription`](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) instance.

```
private async subscribeToPush() {
    try {
      const sub = await this.swPush.requestSubscription({
        serverPublicKey: PUBLIC_VAPID_KEY_OF_SERVER,
      });
      // TODO: Send to server.
    } catch (err) {
      console.error('Could not subscribe due to:', err);
    }
  }
```

A request is rejected if the user denies permission, or if the browser blocks or does not support the Push API or ServiceWorkers. Check [`SwPush.isEnabled`](/api/service-worker/SwPush#isEnabled) to confirm status.

Invoke Push Notifications by pushing a message with the following payload.

```
{
  "notification": {
    "actions": NotificationAction[],
    "badge": USVString,
    "body": DOMString,
    "data": any,
    "dir": "auto"|"ltr"|"rtl",
    "icon": USVString,
    "image": USVString,
    "lang": DOMString,
    "renotify": boolean,
    "requireInteraction": boolean,
    "silent": boolean,
    "tag": DOMString,
    "timestamp": DOMTimeStamp,
    "title": DOMString,
    "vibrate": number[]
  }
}
```

Only `title` is required. See `Notification` [instance properties](https://developer.mozilla.org/en-US/docs/Web/API/Notification#Instance_properties).

While the subscription is active, Service Worker listens for [PushEvent](https://developer.mozilla.org/en-US/docs/Web/API/PushEvent) occurrences and creates [Notification](https://developer.mozilla.org/en-US/docs/Web/API/Notification) instances in response.

Unsubscribe using [`SwPush.unsubscribe()`](/api/service-worker/SwPush#unsubscribe).

An application can subscribe to [`SwPush.notificationClicks`](/api/service-worker/SwPush#notificationClicks) observable to be notified when a user clicks on a notification. For example:

```
this.swPush.notificationClicks.subscribe(({action, notification}) => {
      // TODO: Do something in response to notification click.
    });
```

You can read more on handling notification clicks in the [Service worker notifications guide](ecosystem/service-workers/push-notifications).

Jump to details

----
url: https://angular.dev/api/common/NgForOfContext
----

The `ngFor` directive is deprecated. Use the `@for` block instead.

## [API](#api)

```
    
      class NgForOfContext<T, U extends NgIterable<T> = NgIterable<T>> {}
    
    
```

@param$implicit`T`

Reference to the current item from the collection.

@paramngForOf`U`

The value of the iterable expression. Useful when the expression is more complex then a property access, for example when using the async pipe (`userStreams | async`).

@paramindex`number`

Returns an index of the current item in the collection.

@paramcount`number`

Returns total amount of items in the collection.

@returns`NgForOfContext<T, U>`

Reference to the current item from the collection.

The value of the iterable expression. Useful when the expression is more complex then a property access, for example when using the async pipe (`userStreams | async`).

Returns an index of the current item in the collection.

Returns total amount of items in the collection.

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/copyArrayItem
----

```
function copyArrayItem<T = any>(
  currentArray: T[],
  targetArray: T[],
  currentIndex: number,
  targetIndex: number,
): void;
```

@paramcurrentArray`T[]`

Array from which to copy the item.

@paramtargetArray`T[]`

Array into which is copy the item.

@paramcurrentIndex`number`

Index of the item in its current array.

@paramtargetIndex`number`

Index at which to insert the item.

@returns`void`

Jump to details

----
url: https://angular.dev/api/common/NgSwitchCase
----

Use the `@case` block within a `@switch` block instead. Intent to remove in a future major release

## [API](/api/common/NgSwitchCase#api)

```
    
      class NgSwitchCase implements DoCheck {}
    
    
```

@paramviewContainer`ViewContainerRef`

@paramtemplateRef`TemplateRef<Object>`

@paramngSwitch`NgSwitch`

@returns`NgSwitchCase`

@deprecated

Use the `@case` block within a `@switch` block instead. Intent to remove in a future major release

Stores the HTML template to be selected on match.

## [Description](/api/common/NgSwitchCase#description)

Provides a switch case expression to match against an enclosing `ngSwitch` expression. When the expressions match, the given [`NgSwitchCase`](/api/common/NgSwitchCase) template is rendered. If multiple match expressions match the switch expression value, all of them are displayed.

## [Exported by](/api/common/NgSwitchCase#exported-by)

* `CommonModule`

## [Usage Notes](/api/common/NgSwitchCase#usage-notes)

Within a switch container, `*ngSwitchCase` statements specify the match expressions as attributes. Include `*ngSwitchDefault` as the final case.

```
<container-element [ngSwitch]="switch_expression">
  <some-element *ngSwitchCase="match_expression_1">...</some-element>
  ...
  <some-element *ngSwitchDefault>...</some-element>
</container-element>
```

Each switch-case statement contains an in-line HTML template or template reference that defines the subtree to be selected if the value of the match expression matches the value of the switch expression.

As of Angular v17 the NgSwitch directive uses strict equality comparison (`===`) instead of loose equality (`==`) to match different cases.

Jump to details

----
url: https://angular.dev/api/common/http/testing/provideHttpClientTesting
----

```
function provideHttpClientTesting(): Provider[];
```

@returns`Provider[]`

Jump to details

----
url: https://angular.dev/api/core/testing/ComponentFixtureAutoDetect
----

```
    
      const ComponentFixtureAutoDetect: InjectionToken<boolean>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/minLengthError
----

```
function minLengthError(
  minLength: number,
  options: WithFieldTree<ValidationErrorOptions>,
): MinLengthValidationError;
function minLengthError(
  minLength: number,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<MinLengthValidationError>;
```

Create a minLength error associated with the target field

@paramminLength`number`

The minLength constraint

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`MinLengthValidationError`

Create a minLength error

@paramminLength`number`

The minLength constraint

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<MinLengthValidationError>`

Jump to details

----
url: https://angular.dev/guide/testing/debugging
----

If your tests aren't working as you expect, you can debug them in both the default Node.js environment and in a real browser.

## [Debugging in Node.js](#debugging-in-nodejs)

Debugging in the default Node.js environment is often the quickest way to diagnose issues that are not related to browser-specific APIs or rendering.

1. Run the `ng test` command with the `--debug` flag:
   ```
   ng test --debug
   ```
2. The test runner will start in debug mode and wait for a debugger to attach.
3. You can now attach your preferred debugger. For example, you can use the built-in Node.js debugger in VS Code or the Chrome DevTools for Node.js.

## [Debugging in a browser](#debugging-in-a-browser)

The same way you start a debugging session with in Node, you can use `ng test` with the `--debug` flag with Vitest and [browser mode](/guide/testing/migrating-to-vitest#5-configure-browser-mode-optional).

The test runner will start in debug mode and wait for you to open the browser devtools to debug the tests.

----
url: https://angular.dev/api/animations/animation
----

```
function animation(
  steps: AnimationMetadata | AnimationMetadata[],
  options?: AnimationOptions | null,
): AnimationReferenceMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramsteps`AnimationMetadata | AnimationMetadata[]`

One or more animation objects, as returned by the [`animate()`](/api/animations/animate) or [`sequence()`](/api/animations/sequence) function, that form a transformation from one state to another. A sequence is used by default when you pass an array.

@paramoptions`AnimationOptions | null`

An options object that can contain a delay value for the start of the animation, and additional developer-defined parameters. Provided values for additional parameters are used as defaults, and override values can be passed to the caller on invocation.

@returns`AnimationReferenceMetadata`

An object that encapsulates the animation data.

## [Usage Notes](#usage-notes)

The following example defines a reusable animation, providing some default parameter values.

```
var fadeAnimation = animation([
  style({ opacity: '{{ start }}' }),
  animate('{{ time }}',
  style({ opacity: '{{ end }}'}))
  ],
  { params: { time: '1000ms', start: 0, end: 1 }});
```

The following invokes the defined animation with a call to [`useAnimation()`](/api/animations/useAnimation), passing in override parameter values.

```
useAnimation(fadeAnimation, {
  params: {
    time: '2s',
    start: 1,
    end: 0
  }
})
```

If any of the passed-in parameter values are missing from this call, the default values are used. If one or more parameter values are missing before a step is animated, [`useAnimation()`](/api/animations/useAnimation) throws an error.

Jump to details

----
url: https://angular.dev/api/aria/tree/TreeItem
----

```
    
      class TreeItem<V> extends DeferredContentAware implements OnInit ,OnDestroy ,HasElement {}
    
    
```

A reference to the host element.

A unique identifier for the tree item.

The value of the tree item.

The parent tree root or tree item group.

Whether the tree item is disabled.

Whether the tree item is selectable.

Whether the tree item is expanded.

Optional label for typeahead. Defaults to the element's textContent.

Search term for typeahead.

The tree root.

Whether the item is active.

The level of the current item in a tree.

Whether the item is selected.

Whether this item is visible due to all of its parents being expanded.

@returns`void`

@returns`void`

## [Description](#description)

A selectable and expandable item in an `ngTree`.

The `ngTreeItem` directive represents an individual node within an `ngTree`. It can be selected, expanded (if it has children), and disabled. The `parent` input establishes the hierarchical relationship within the tree.

```
<li ngTreeItem [parent]="parentTreeOrGroup" value="item-id" label="Item Label">
  Item Label
</li>
```

Jump to details

----
url: https://angular.dev/api/core/globals/getHostElement
----

```
function getHostElement(componentOrDirective: {}): Element;
```

@paramcomponentOrDirective`{}`

Component or directive instance for which the host element should be retrieved.

@returns`Element`

Host element of the target.

Jump to details

----
url: https://angular.dev/api/forms/signals/MaybeSchemaPathTree
----

```
    
      type MaybeSchemaPathTree<TModel, TPathKind extends PathKind = PathKind.Root> = (TModel & undefined) | SchemaPathTree<Exclude<TModel, undefined>, TPathKind>
    
    
```

## [Description](#description)

Helper type for defining `FieldPath`. Given a type `TValue` that may include `undefined`, it extracts the `undefined` outside the `FieldPath` type.

For example `MaybeFieldPath<{a: number} | undefined, PathKind.Child>` would be equivalent to `undefined | FieldTree<{a: number}, PathKind.child>`.

Jump to details

----
url: https://angular.dev/api/router/UrlHandlingStrategy
----

```
    
      abstract class UrlHandlingStrategy {}
    
    
```

Tells the router if this URL should be processed.

When it returns true, the router will execute the regular navigation. When it returns false, the router will set the router state to an empty state. As a result, all the active components will be destroyed.

@paramurl`UrlTree`

@returns`boolean`

Extracts the part of the URL that should be handled by the router. The rest of the URL will remain untouched.

@paramurl`UrlTree`

@returns`UrlTree`

Merges the URL fragment with the rest of the URL.

@paramnewUrlPart`UrlTree`

@paramrawUrl`UrlTree`

@returns`UrlTree`

Jump to details

----
url: https://angular.dev/api/aria/grid/Grid
----

```
    
      class Grid implements OnDestroy {}
    
    
```

A reference to the host element.

Text direction.

Whether selection is enabled for the grid.

Whether the grid is disabled.

Whether to allow disabled items to receive focus. When `true`, disabled items are focusable but not interactive. When `false`, disabled items are skipped during navigation.

The focus strategy used by the grid.

* `roving`: Focus is moved to the active cell using `tabindex`.
* `activedescendant`: Focus remains on the grid container, and `aria-activedescendant` is used to indicate the active cell.

The wrapping behavior for keyboard navigation along the row axis.

* `continuous`: Navigation wraps from the last row to the first, and vice-versa.
* `loop`: Navigation wraps within the current row.
* `nowrap`: Navigation stops at the first/last item in the row.

The wrapping behavior for keyboard navigation along the column axis.

* `continuous`: Navigation wraps from the last column to the first, and vice-versa.
* `loop`: Navigation wraps within the current column.
* `nowrap`: Navigation stops at the first/last item in the column.

Whether multiple cells in the grid can be selected.

The selection strategy used by the grid.

* `follow`: The focused cell is automatically selected.
* `explicit`: Cells are selected explicitly by the user (e.g., via click or spacebar).

The tabindex of the grid.

The ID of the active descendant in the grid.

@returns`void`

Scrolls the active cell into view.

@paramoptions`ScrollIntoViewOptions`

@returns`void`

## [Description](#description)

The container for a grid. It provides keyboard navigation and focus management for the grid's rows and cells. It manages the overall behavior of the grid, including focus wrapping, selection, and disabled states.

```
<table ngGrid [multi]="true" [enableSelection]="true">
  @for (row of gridData; track row) {
    <tr ngGridRow>
      @for (cell of row; track cell) {
        <td ngGridCell [disabled]="cell.disabled">
          {{cell.value}}
        </td>
      }
    </tr>
  }
</table>
```

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragSortEvent
----

```
    
      interface CdkDragSortEvent<T = any, I = T> {}
    
    
```

Index from which the item was sorted previously.

Index that the item is currently in.

Container that the item belongs to.

Item that is being sorted.

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/Point
----

```
    
      interface Point {}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/GuardsCheckStart
----

```
    
      class GuardsCheckStart extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramurlAfterRedirects`string`

@paramstate`RouterStateSnapshot`

@returns`GuardsCheckStart`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/api/ssr/withRoutes
----

```
function withRoutes(
  routes: ServerRoute[],
): ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes>;
```

@paramroutes`ServerRoute[]`

* An array of [`ServerRoute`](/api/ssr/ServerRoute) objects, each defining a server-rendered route.

@returns`ServerRenderingFeature<ServerRenderingFeatureKind.ServerRoutes>`

A `ServerRenderingFeature` object configuring server-side routes.

## [Description](#description)

Configures server-side routing for the application.

This function registers an array of [`ServerRoute`](/api/ssr/ServerRoute) definitions, enabling server-side rendering for specific URL paths. These routes are used to pre-render content on the server, improving initial load performance and SEO.

## [Usage Notes](#usage-notes)

```
import { provideServerRendering, withRoutes, ServerRoute, RenderMode } from '@angular/ssr';

const serverRoutes: ServerRoute[] = [
  {
    path: '', // This renders the "/" route on the client (CSR)
    renderMode: RenderMode.Client,
  },
  {
    path: 'about', // This page is static, so we prerender it (SSG)
    renderMode: RenderMode.Prerender,
  },
  {
    path: 'profile', // This page requires user-specific data, so we use SSR
    renderMode: RenderMode.Server,
  },
  {
    path: '**', // All other routes will be rendered on the server (SSR)
    renderMode: RenderMode.Server,
  },
];

provideServerRendering(withRoutes(serverRoutes));
```

Jump to details

----
url: https://angular.dev/guide/templates/binding
----

In Angular, a **binding** creates a dynamic connection between a component's template and its data. This connection ensures that changes to the component's data automatically update the rendered template.

## [Render dynamic text with text interpolation](#render-dynamic-text-with-text-interpolation)

You can bind dynamic text in templates with double curly braces, which tells Angular that it is responsible for the expression inside and ensuring it is updated correctly. This is called **text interpolation**.

```
@Component({
  template: `
    <p>Your color preference is {{ theme }}.</p>
  `,
  ...
})
export class App {
  theme = 'dark';
}
```

In this example, when the snippet is rendered to the page, Angular will replace `{{ theme }}` with `dark`.

```
<!-- Rendered Output -->
<p>Your color preference is dark.</p>
```

Bindings that change over time should read values from [signals](/guide/signals). Angular tracks the signals read in the template, and updates the rendered page when those signal values change.

```
@Component({
  template: `
    <!-- Does not necessarily update when `welcomeMessage` changes. -->
    <p>{{ welcomeMessage }}</p>

    <p>Your color preference is {{ theme() }}.</p> <!-- Always updates when the value of the `theme` signal changes. -->
  `
  ...
})
export class App {
  welcomeMessage = "Welcome, enjoy this app that we built for you";
  theme = signal('dark');
}
```

For more details, see the [Signals guide](/guide/signals).

Continuing the theme example, if a user clicks on a button that updates the `theme` signal to `'light'` after the page loads, the page updates accordingly to:

```
<!-- Rendered Output -->
<p>Your color preference is light.</p>
```

```
<!-- Bind the `disabled` property on the button element's DOM object -->
<button [disabled]="isFormValid()">Save</button>
```

In this example, every time `isFormValid` changes, Angular automatically sets the `disabled` property of the `HTMLButtonElement` instance.

### [Component and directive properties](#component-and-directive-properties)

When an element is an Angular component, you can use property bindings to set component input properties using the same square bracket syntax.

```
<!-- Bind the `value` property on the `MyListbox` component instance. -->
<my-listbox [value]="mySelection()" />
```

In this example, every time `mySelection` changes, Angular automatically sets the `value` property of the `MyListbox` instance.

You can bind to directive properties as well.

```
<!-- Bind to the `ngSrc` property of the `NgOptimizedImage` directive  -->
<img [ngSrc]="profilePhotoUrl()" alt="The current user's profile photo" />
```

### [Attributes](#attributes)

When you need to set HTML attributes that do not have corresponding DOM properties, such as SVG attributes, you can bind attributes to elements in your template with the `attr.` prefix.

```
<!-- Bind the `role` attribute on the `<ul>` element to the component's `listRole` property. -->
<ul [attr.role]="listRole()">
```

In this example, every time `listRole` changes, Angular automatically sets the `role` attribute of the `<ul>` element by calling `setAttribute`.

If the value of an attribute binding is `null`, Angular removes the attribute by calling `removeAttribute`.

### [Text interpolation in properties and attributes](#text-interpolation-in-properties-and-attributes)

You can also use text interpolation syntax in properties and attributes by using the double curly brace syntax instead of square braces around the property or attribute name. When using this syntax, Angular treats the assignment as a property binding.

```
<!-- Binds a value to the `alt` property of the image element's DOM object. -->
<img src="profile-photo.jpg" alt="Profile photo of {{ firstName() }}" />
```

## [CSS class and style property bindings](#css-class-and-style-property-bindings)

Angular supports additional features for binding CSS classes and CSS style properties to elements.

### [CSS classes](#css-classes)

You can create a CSS class binding to conditionally add or remove a CSS class on an element based on whether the bound value is [truthy or falsy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy).

```
<!-- When `isExpanded` is truthy, add the `expanded` CSS class. -->
<ul [class.expanded]="isExpanded()">
```

You can also bind directly to the `class` property. Angular accepts three types of value:

| Description of `class` value                                                                                                                                      | TypeScript type       |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| A string containing one or more CSS classes separated by spaces                                                                                                   | `string`              |
| An array of CSS class strings                                                                                                                                     | `string[]`            |
| An object where each property name is a CSS class name and each corresponding value determines whether that class is applied to the element, based on truthiness. | `Record<string, any>` |

```
@Component({
  template: `
    <ul [class]="listClasses"> ... </ul>
    <section [class]="sectionClasses()"> ... </section>
    <button [class]="buttonClasses()"> ... </button>
  `,
  ...
})
export class UserProfile {
  listClasses = 'full-width outlined';
  sectionClasses = signal(['expandable', 'elevated']);
  buttonClasses = signal({
    highlighted: true,
    embiggened: false,
  });
}
```

The above example renders the following DOM:

```
<ul class="full-width outlined"> ... </ul>
<section class="expandable elevated"> ... </section>
<button class="highlighted"> ... </button>
```

Angular ignores any string values that are not valid CSS class names.

When using static CSS classes, directly binding `class`, and binding specific classes, Angular intelligently combines all of the classes in the rendered result.

```
@Component({
  template: `<ul class="list" [class]="listType()" [class.expanded]="isExpanded()"> ...`,
  ...
})
export class Listbox {
  listType = signal('box');
  isExpanded = signal(true);
}
```

In the example above, Angular renders the `ul` element with all three CSS classes.

```
<ul class="list box expanded">
```

Angular does not guarantee any specific order of CSS classes on rendered elements.

When binding `class` to an array or an object, Angular compares the previous value to the current value with the triple-equals operator (`===`). You must create a new object or array instance when you modify these values in order for Angular to apply any updates.

If an element has multiple bindings for the same CSS class, Angular resolves collisions by following its style precedence order.

**NOTE:** Class bindings do not support space-separated class names in a single key. They also don't support mutations on objects as the reference of the binding remains the same. If you need one or the other, use the [ngClass](/api/common/NgClass) directive.

### [CSS style properties](#css-style-properties)

You can also bind to CSS style properties directly on an element.

```
<!-- Set the CSS `display` property based on the `isExpanded` property. -->
<section [style.display]="isExpanded() ? 'block' : 'none'">
```

You can further specify units for CSS properties that accept units.

```
<!-- Set the CSS `height` property to a pixel value based on the `sectionHeightInPixels` property. -->
<section [style.height.px]="sectionHeightInPixels()">
```

You can also set multiple style values in one binding. Angular accepts the following types of value:

| Description of `style` value                                                                                              | TypeScript type       |
| ------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| A string containing zero or more CSS declarations, such as `"display: flex; margin: 8px"`.                                | `string`              |
| An object where each property name is a CSS property name and each corresponding value is the value of that CSS property. | `Record<string, any>` |

```
@Component({
  template: `
    <ul [style]="listStyles()"> ... </ul>
    <section [style]="sectionStyles()"> ... </section>
  `,
  ...
})
export class UserProfile {
  listStyles = signal('display: flex; padding: 8px');
  sectionStyles = signal({
    border: '1px solid black',
    'font-weight': 'bold',
  });
}
```

The above example renders the following DOM.

```
<ul style="display: flex; padding: 8px"> ... </ul>
<section style="border: 1px solid black; font-weight: bold"> ... </section>
```

When binding `style` to an object, Angular compares the previous value to the current value with the triple-equals operator (`===`). You must create a new object instance when you modify these values in order for Angular to apply any updates.

If an element has multiple bindings for the same style property, Angular resolves collisions by following its style precedence order.

## [ARIA attributes](#aria-attributes)

Angular supports binding string values to ARIA attributes.

```
<button type="button" [aria-label]="actionLabel()">
  {{ actionLabel() }}
</button>
```

Angular writes the string value to the element’s `aria-label` attribute and removes it when the bound value is `null`.

Some ARIA features expose DOM properties or directive inputs that accept structured values (such as element references). Use standard property bindings for those cases. See the [accessibility guide](/best-practices/a11y#aria-attributes-and-properties) for examples and additional guidance.

----
url: https://angular.dev/api/cdk/testing/ElementDimensions
----

```
    
      interface ElementDimensions {}
    
    
```

The distance from the top of the viewport in pixels

The distance from the left of the viewport in pixels

The width of the element in pixels

The height of the element in pixels

Jump to details

----
url: https://angular.dev/api/forms/FormArrayName
----

```
    
      class FormArrayName extends ControlContainer implements OnInit ,OnDestroy {}
    
    
```

@paramparent`ControlContainer`

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@returns`FormArrayName`

Tracks the name of the [`FormArray`](/api/forms/FormArray) bound to the directive. The name corresponds to a key in the parent [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray). Accepts a name as a string or a number. The name in the form of a string is useful for individual forms, while the numerical form allows for form arrays to be bound to indices when iterating over arrays in a [`FormArray`](/api/forms/FormArray).

The [`FormArray`](/api/forms/FormArray) bound to this directive.

The top-level directive for this group if present, otherwise null.

Returns an array that represents the path from the top-level form to this control. Each index is the string name of the control on that level.

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Syncs a nested [`FormArray`](/api/forms/FormArray) to a DOM element.

This directive is designed to be used with a parent [`FormGroupDirective`](/api/forms/FormGroupDirective)/[`FormArrayDirective`](/api/forms/FormArrayDirective) (selector: `[formGroup]`/`[formArray]`).

It accepts the string name of the nested [`FormArray`](/api/forms/FormArray) you want to link, and will look for a [`FormArray`](/api/forms/FormArray) registered with that name in the parent [`FormGroup`](/api/forms/FormGroup)/[`FormArray`](/api/forms/FormArray) instance you passed into [`FormGroupDirective`](/api/forms/FormGroupDirective)/[`FormArrayDirective`](/api/forms/FormArrayDirective).

## [Exported by](#exported-by)

* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

### [Example](#example)

```
import {Component} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div formArrayName="cities">
        @for (city of cities.controls; track city) {
          <input [formControlName]="$index" placeholder="City" />
        }
      </div>
      <button>Submit</button>
    </form>

    <button (click)="addCity()">Add City</button>
    <button (click)="setPreset()">Set preset</button>
  `,
  standalone: false,
})
export class NestedFormArray {
  form = new FormGroup({
    cities: new FormArray([new FormControl('SF'), new FormControl('NY')]),
  });

  get cities(): FormArray {
    return this.form.get('cities') as FormArray;
  }

  addCity() {
    this.cities.push(new FormControl());
  }

  onSubmit() {
    console.log(this.cities.value); // ['SF', 'NY']
    console.log(this.form.value); // { cities: ['SF', 'NY'] }
  }

  setPreset() {
    this.cities.patchValue(['LA', 'MTV']);
  }
}
```

Jump to details

----
url: https://angular.dev/api/forms/signals/ValidationErrorOptions
----

```
    
      interface ValidationErrorOptions {}
    
    
```

Human readable error message.

Jump to details

----
url: https://angular.dev/api/router/mapToResolve
----

```
function mapToResolve<T>(provider: Type<Resolve<T>>): ResolveFn<T>;
```

@paramprovider`Type<Resolve<T>>`

@returns`ResolveFn<T>`

## [Description](#description)

Maps an injectable class with a resolve function to an equivalent [`ResolveFn`](/api/router/ResolveFn) for use in a [`Route`](/api/router/Route) definition.

Usage

```
@Injectable({providedIn: 'root'})
export class ResolveUser {
  resolve() {
    return {name: 'Bob'};
  }
}

const userRoute: Route = {
  path: 'user',
  resolve: {
    user: mapToResolve(ResolveUser),
  },
};
```

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/15-forms
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/14-routerLink)[](/tutorials/learn-angular/16-form-control-values)

[](/tutorials/learn-angular/14-routerLink)[](/tutorials/learn-angular/16-form-control-values)

Forms are a big part of many apps because they enable your app to accept user input. Let's learn about how forms are handled in Angular.

In Angular, there are two types of forms: template-driven and reactive. You'll learn about both over the next few activities.

**NOTE:** Learn more about [forms in Angular in the in-depth guide](/guide/forms).

In this activity, you'll learn how to set up a form using a template-driven approach.

***

1. ### [Create an input field](#create-an-input-field)

   In `user.ts`, update the template by adding a text input with the `id` set to `framework`, type set to `text`.

   ```
   <label for="framework">
     Favorite Framework:
     <input id="framework" type="text" />
   </label>
   ```

2. ### [Import `FormsModule`](#import-formsmodule)

   For this form to use Angular features that enable data binding to forms, you'll need to import the [`FormsModule`](/api/forms/FormsModule).

   Import the [`FormsModule`](/api/forms/FormsModule) from `@angular/forms` and add it to the `imports` array of the `User`.

   ```
   import {Component} from '@angular/core';
   import {FormsModule} from '@angular/forms';

   @Component({
   ...
   imports: [FormsModule],
   })
   export class User {}
   ```

3. ### [Add binding to the value of the input](#add-binding-to-the-value-of-the-input)

   The [`FormsModule`](/api/forms/FormsModule) has a directive called [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) that binds the value of the input to a property in your class.

   Update the input to use the [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) directive, specifically with the following syntax `[(ngModel)]="favoriteFramework"` to bind to the `favoriteFramework` property.

   ```
   <label for="framework">
     Favorite Framework:
     <input id="framework" type="text" [(ngModel)]="favoriteFramework" />
   </label>
   ```

   After you've made changes, try entering a value in the input field. Notice how it updates on the screen (yes, very cool).

   **NOTE:** The syntax `[()]` is known as "banana in a box" but it represents two-way binding: property binding and event binding. Learn more in the [Angular docs about two-way data binding](guide/templates/two-way-binding).

You've now taken an important first step towards building forms with Angular.

Nice work. Let's keep the momentum going!

----
url: https://angular.dev/api/core/makeEnvironmentProviders
----

```
function makeEnvironmentProviders(
  providers: (Provider | EnvironmentProviders)[],
): EnvironmentProviders;
```

@paramproviders`(Provider | EnvironmentProviders)[]`

@returns`EnvironmentProviders`

Jump to details

----
url: https://angular.dev/api/core/testing/withModule
----

```
function withModule(moduleDef: TestModuleMetadata): InjectSetupWrapper;
function withModule(moduleDef: TestModuleMetadata, fn: Function): () => any;
```

@parammoduleDef`TestModuleMetadata`

@returns`InjectSetupWrapper`

@parammoduleDef`TestModuleMetadata`

@paramfn`Function`

@returns`() => any`

Jump to details

----
url: https://angular.dev/api/forms/FormControlState
----

```
    
      interface FormControlState<T> {}
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/emailError
----

```
function emailError(
  options: WithFieldTree<ValidationErrorOptions>,
): EmailValidationError;
function emailError(
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<EmailValidationError>;
```

Create an email format error associated with the target field

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`EmailValidationError`

Create an email format error

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<EmailValidationError>`

Jump to details

----
url: https://angular.dev/api/core/ProviderToken
----

```
    
      type ProviderToken<T> = Type<T> | AbstractType<T> | InjectionToken<T>
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/Debouncer
----

```
    
      type Debouncer<TValue, TPathKind extends PathKind = PathKind.Root> = (  context: FieldContext<TValue, TPathKind>,  abortSignal: AbortSignal,) => Promise<void> | void
    
    
```

Jump to details

----
url: https://angular.dev/
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

# [Angular v22 is here!](events/v22)

[⚡ Explore what's new](events/v22)

search

Productivity\
Meets scalability
-----------------

### The framework for building scalable web apps with confidence

[](#learn-more)

Features that actually\
help you solve problems
-----------------------

Signals

Control Flow

Deferrable Views

Hydration

Enabling you to build\
smarter and faster
------------------

----
url: https://angular.dev/api/common/CurrencyPipe
----

```
{{ value_expression | currency:currencyCode:display:digitsInfo:locale }}
```

## [API](#api)

```
    
      class CurrencyPipe implements PipeTransform {
}
    
    
```

@param\_locale`string`

@param\_defaultCurrencyCode`string`

@returns`CurrencyPipe`

@paramvalue`string | number`

The number to be formatted as currency.

@paramcurrencyCode`string | undefined`

The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such as `USD` for the US dollar and `EUR` for the euro. The default currency code can be configured using the [`DEFAULT_CURRENCY_CODE`](/api/core/DEFAULT_CURRENCY_CODE) injection token.

@paramdisplay`string | boolean | undefined`

The format for the currency indicator. One of the following:

* `code`: Show the code (such as `USD`).
* `symbol`(default): Show the symbol (such as `$`).
* `symbol-narrow`: Use the narrow symbol for locales that have two symbols for their currency. For example, the Canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`. If the locale has no narrow symbol, uses the standard symbol for the locale.
* String: Use the given string value instead of a code or a symbol. For example, an empty string will suppress the currency & symbol.
* Boolean (marked deprecated in v5): `true` for symbol and false for `code`.

@paramdigitsInfo`string | undefined`

Decimal representation options, specified by a string in the following format:\
`{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}`.

* `minIntegerDigits`: The minimum number of integer digits before the decimal point. Default is `1`.
* `minFractionDigits`: The minimum number of digits after the decimal point. Default is `2`.
* `maxFractionDigits`: The maximum number of digits after the decimal point. Default is `2`. If not provided, the number will be formatted with the proper amount of digits, depending on what the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) specifies. For example, the Canadian dollar has 2 digits, whereas the Chilean peso has none.

@paramlocale`string | undefined`

A locale code for the locale format rules to use. When not supplied, uses the value of [`LOCALE_ID`](/api/core/LOCALE_ID), which is `en-US` by default. See [Setting your app locale](guide/i18n/locale-id).

@returns`string | null`

@paramvalue`null | undefined`

@paramcurrencyCode`string | undefined`

@paramdisplay`string | boolean | undefined`

@paramdigitsInfo`string | undefined`

@paramlocale`string | undefined`

@returns`null`

@paramvalue`string | number | null | undefined`

@paramcurrencyCode`string | undefined`

@paramdisplay`string | boolean | undefined`

@paramdigitsInfo`string | undefined`

@paramlocale`string | undefined`

@returns`string | null`

## [Description](#description)

Transforms a number to a currency string, formatted according to locale rules that determine group sizing and separator, decimal-point character, and other locale-specific configurations.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

The following code shows how the pipe transforms numbers into text strings, according to various format specifications, where the caller's default locale is `en-US`.

```
@Component({
  selector: 'currency-pipe',
  imports: [CurrencyPipe],
  template: `<div>
    <!--output '$0.26'-->
    <p>A: {{ a | currency }}</p>

    <!--output 'CA$0.26'-->
    <p>A: {{ a | currency: 'CAD' }}</p>

    <!--output 'CAD0.26'-->
    <p>A: {{ a | currency: 'CAD' : 'code' }}</p>

    <!--output 'CA$0,001.35'-->
    <p>B: {{ b | currency: 'CAD' : 'symbol' : '4.2-2' }}</p>

    <!--output '$0,001.35'-->
    <p>B: {{ b | currency: 'CAD' : 'symbol-narrow' : '4.2-2' }}</p>

    <!--output '0 001,35 CA$'-->
    <p>B: {{ b | currency: 'CAD' : 'symbol' : '4.2-2' : 'fr' }}</p>

    <!--output 'CLP1' because CLP has no cents-->
    <p>B: {{ b | currency: 'CLP' }}</p>
  </div>`,
})
export class CurrencyPipeComponent {
  a: number = 0.259;
  b: number = 1.3495;
}
```

Jump to details

----
url: https://angular.dev/api/forms/ControlContainer
----

```
    
      abstract class ControlContainer extends AbstractControlDirective {}
    
    
```

The name for the control

The top-level form directive for the control.

The path to this group.

A reference to the underlying control.

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

Jump to details

----
url: https://angular.dev/api/forms/signals/AsyncValidatorOptions
----

## [API](/api/forms/signals/AsyncValidatorOptions#api)

```
    
      interface AsyncValidatorOptions<TValue, TParams, TResult, TPathKind extends PathKind = PathKind.Root> {}
    
    
```

A function that receives the field context and returns the params for the resource.

Duration in milliseconds to wait before triggering the async operation, or a function that returns a promise that resolves when the update should proceed.

A function that receives the resource params and returns a resource of the given params. The given params should be used as is to create the resource. The forms system will report the params as `undefined` when this validation doesn't need to be run.

A function to handle errors thrown by httpResource (HTTP errors, network errors, etc.). Receives the error and the field context, returns a list of validation errors.

A function that takes the resource result, and the current field context and maps it to a list of validation errors.

A function that receives the field context and returns true if the async validation should be run.

Jump to details

----
url: https://angular.dev/api/core/debounced
----

```
function debounced<T>(
  source: () => T,
  wait: NoInfer<DebounceTimer<T>>,
  options?: NoInfer<DebouncedOptions<T>> | undefined,
): Resource<T>;
```

@paramsource`() => T`

The source signal to debounce.

@paramwait`NoInfer<DebounceTimer<T>>`

The amount of time to wait before calling the source signal, or a function that returns a promise that resolves when the debounced value should be updated.

@paramoptions`NoInfer<DebouncedOptions<T>> | undefined`

The options to use for the debounced signal.

@returns`Resource<T>`

A resource representing the debounced signal.

Jump to details

----
url: https://angular.dev/api/forms/RequiredValidator
----

```
    
      class RequiredValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the required attribute bound to this directive.

## [Description](#description)

A directive that adds the `required` validator to any controls marked with the `required` attribute. The directive is provided with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) multi-provider list.

## [Exported by](#exported-by)

* `FormsModule`
* `ReactiveFormsModule`

## [Usage Notes](#usage-notes)

### [Adding a required validator using template-driven forms](#adding-a-required-validator-using-template-driven-forms)

```
<input name="fullName" ngModel required>
```

Jump to details

----
url: https://angular.dev/api/router/Data
----

```
    
      type Data = {  [key: string | symbol]: any;}
    
    
```

Jump to details

----
url: https://angular.dev/api/common/getLocaleCurrencyCode
----

We recommend you create a map of locale to ISO 4217 currency codes. Time relative currency data is provided by the CLDR project. See <https://www.unicode.org/cldr/charts/44/supplemental/detailed_territory_currency_information.html>

## [API](#api)

```
function getLocaleCurrencyCode(locale: string): string | null;
```

@deprecated

We recommend you create a map of locale to ISO 4217 currency codes. Time relative currency data is provided by the CLDR project. See <https://www.unicode.org/cldr/charts/44/supplemental/detailed_territory_currency_information.html>

@paramlocale`string`

The code of the locale whose currency code we want.

@returns`string | null`

The code of the default currency for the given locale.

## [Description](#description)

Retrieves the default currency code for the given locale.

The default is defined as the first currency which is still in use.

Jump to details

----
url: https://angular.dev/api/common/http/HttpClientCommonOptions
----

```
    
      interface HttpClientCommonOptions extends Omit<HttpRequestOptions, 'headers' | 'params'> {}
    
    
```

@deprecated

Use `reportUploadProgress` and `reportDownloadProgress` instead

When set to `true` a request emited against the [`FetchBackend`](/api/common/http/FetchBackend) will throw an error.

This property accepts either a boolean to enable/disable transferring cache for eligible requests performed using [`HttpClient`](/api/common/http/HttpClient), or an object, which allows to configure cache parameters, such as which headers should be included (no headers are included by default).

Setting this property will override the options passed to `provideClientHydration()` for this particular request

Jump to details

----
url: https://angular.dev/api/core/ResourceStatus
----

```
    
      type ResourceStatus = 'idle' | 'error' | 'loading' | 'reloading' | 'resolved' | 'local'
    
    
```

## [Description](#description)

String value capturing the status of a [`Resource`](/api/core/Resource).

Possible statuses are:

`idle` - The resource has no valid request and will not perform any loading. `value()` will be `undefined`.

`loading` - The resource is currently loading a new value as a result of a change in its reactive dependencies. `value()` will be `undefined`.

`reloading` - The resource is currently reloading a fresh value for the same reactive dependencies. `value()` will continue to return the previously fetched value during the reloading operation.

`error` - Loading failed with an error. `value()` will be `undefined`.

`resolved` - Loading has completed and the resource has the value returned from the loader.

`local` - The resource's value was set locally via `.set()` or `.update()`.

Jump to details

----
url: https://angular.dev/api/forms/SelectMultipleControlValueAccessor
----

```
    
      class SelectMultipleControlValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor {}
    
    
```

Tracks the option comparison algorithm for tracking identities when checking for changes.

## [Description](#description)

The [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing multi-select control values and listening to multi-select control changes. The value accessor is used by the [`FormControlDirective`](/api/forms/FormControlDirective), [`FormControlName`](/api/forms/FormControlName), and [`NgModel`](/api/forms/NgModel) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using a multi-select control](#using-a-multi-select-control)

The follow example shows you how to use a multi-select control with a reactive form.

```
const countryControl = new FormControl();
```

```
<select multiple name="countries" [formControl]="countryControl">
  @for(country of countries; track $index) {
     <option [ngValue]="country">{{ country.name }}</option>
  }
</select>
```

### [Customizing option selection](#customizing-option-selection)

To customize the default option comparison algorithm, `<select>` supports `compareWith` input. See the [`SelectControlValueAccessor`](/api/forms/SelectControlValueAccessor) for usage.

Jump to details

----
url: https://angular.dev/api/ssr/provideServerRendering
----

```
function provideServerRendering(
  ...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]
): EnvironmentProviders;
function provideServerRendering(
  options: ServerRenderingOptions,
  ...features: ServerRenderingFeature<ServerRenderingFeatureKind>[]
): EnvironmentProviders;
```

Configures server-side rendering for an Angular application.

This function sets up the necessary providers for server-side rendering, including support for server routes and app shell. It combines features configured using [`withRoutes`](/api/ssr/withRoutes) and [`withAppShell`](/api/ssr/withAppShell) to provide a comprehensive server-side rendering setup.

@paramfeatures`ServerRenderingFeature<ServerRenderingFeatureKind>[]`

* Optional features to configure additional server rendering behaviors.

@returns`EnvironmentProviders`

An [`EnvironmentProviders`](/api/core/EnvironmentProviders) instance with the server-side rendering configuration.

Usage notes

Basic example of how you can enable server-side rendering in your application when using the `bootstrapApplication` function:

```
import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
import { AppComponent } from './app/app.component';
import { SERVER_ROUTES } from './app/app.server.routes';
import { AppShellComponent } from './app/app-shell.component';

const bootstrap = (context: BootstrapContext) =>
    bootstrapApplication(AppComponent, {
      providers: [
        provideServerRendering(
          withRoutes(SERVER_ROUTES),
          withAppShell(AppShellComponent),
        ),
      ],
    }, context);

export default bootstrap;
```

Configures server-side rendering for an Angular application with additional options.

This function sets up the necessary providers for server-side rendering, including support for server routes and app shell. It combines features configured using [`withRoutes`](/api/ssr/withRoutes) and [`withAppShell`](/api/ssr/withAppShell) to provide a comprehensive server-side rendering setup.

@paramoptions`ServerRenderingOptions`

* Configuration options for server-side rendering.

@paramfeatures`ServerRenderingFeature<ServerRenderingFeatureKind>[]`

* Optional features to configure additional server rendering behaviors.

@returns`EnvironmentProviders`

An [`EnvironmentProviders`](/api/core/EnvironmentProviders) instance with the server-side rendering configuration.

Usage notes

Basic example of how you can enable server-side rendering with options in your application when using the `bootstrapApplication` function:

```
import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
import { AppComponent } from './app/app.component';
import { SERVER_ROUTES } from './app/app.server.routes';
import { AppShellComponent } from './app/app-shell.component';

const bootstrap = (context: BootstrapContext) =>
    bootstrapApplication(AppComponent, {
      providers: [
        provideServerRendering(
          { maxResponseBodySize: 1024 * 1024 }, // 1MB limit
          withRoutes(SERVER_ROUTES),
          withAppShell(AppShellComponent),
        ),
      ],
    }, context);

export default bootstrap;
```

## [Description](#description)

Configures server-side rendering for an Angular application.

This function sets up the necessary providers for server-side rendering, including support for server routes and app shell. It combines features configured using [`withRoutes`](/api/ssr/withRoutes) and [`withAppShell`](/api/ssr/withAppShell) to provide a comprehensive server-side rendering setup.

## [Usage Notes](#usage-notes)

Basic example of how you can enable server-side rendering in your application when using the `bootstrapApplication` function:

```
import { bootstrapApplication, BootstrapContext } from '@angular/platform-browser';
import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
import { AppComponent } from './app/app.component';
import { SERVER_ROUTES } from './app/app.server.routes';
import { AppShellComponent } from './app/app-shell.component';

const bootstrap = (context: BootstrapContext) =>
    bootstrapApplication(AppComponent, {
      providers: [
        provideServerRendering(
          withRoutes(SERVER_ROUTES),
          withAppShell(AppShellComponent),
        ),
      ],
    }, context);

export default bootstrap;
```

Jump to details

----
url: https://angular.dev/overview
----

# What is Angular?

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/what-is-angular.md "Edit this page")

Angular is a web framework that empowers\
developers to build fast, reliable applications that users love.

Maintained by a dedicated team at Google, Angular provides a broad suite of tools, APIs, and libraries to simplify and streamline your development workflow. Angular gives you a solid platform on which to build fast, reliable applications that scale with both the size of your team and the size of your codebase. Angular.dev is the official home for Angular documentation.

###### Want to see some code?

### [Essentials](essentials)

[An overview of what it's like to use Angular](essentials)

### [Tutorials](tutorials)

[Step-by-step instructions in your browser](tutorials)

## [Features that power your development](#features-that-power-your-development)

### [Components](essentials/components)

[Angular components make it easy to split your code into well-encapsulated parts.](essentials/components)

[Get started with Components](essentials/components)

### [Angular Signals](essentials/signals)

[Our fine-grained reactivity model, combined with compile-time optimizations, simplifies development and helps build faster apps by default.](essentials/signals)

[Explore Angular Signals](essentials/signals)

### [Server-side rendering](guide/ssr)

[Angular supports both server-side rendering (SSR) and static site generation (SSG) along with full DOM hydration.](guide/ssr)

[Read about SSR](guide/ssr)

### [Dependency injection](essentials/dependency-injection)

[Easily share code across components throughout your entire application.](essentials/dependency-injection)

[Explore Dependency Injection](essentials/dependency-injection)

### [Angular Routing](guide/routing)

[Provides a feature-rich navigation toolkit, including support for route guards, data resolution, lazy-loading, and much more.](guide/routing)

[Explore Angular Routing](guide/routing)

### [Forms in Angular](guide/forms)

[Provides a standardized system for form participation and validation.](guide/forms)

[Explore Forms](guide/forms)

## [Develop applications faster than ever](#develop-applications-faster-than-ever)

### [CLI](tools/cli)

[Angular CLI gets your project running in under a minute with the commands you need to grow into a deployed production application.](tools/cli)

[Angular CLI](tools/cli)

### [DevTools](tools/devtools)

[Angular DevTools sits alongside your browser's developer tools. It helps debug and analyze your app, including a component tree inspector, dependency injection tree view, and custom performance profiling flame chart.](tools/devtools)

[Angular DevTools](tools/devtools)

### [ng update](update)

[Angular CLI's `ng update` runs automated code transformations that automatically handle routine breaking changes, dramatically simplifying major version updates. Keeping up with the latest version keeps your app as fast and secure as possible.](update)

[ng update](update)

### [Language Service](tools/language-service)

[Angular's IDE language service powers code completion, navigation, refactoring, and real-time diagnostics in your favorite editor.](tools/language-service)

[Language Service](tools/language-service)

## [Ship with confidence](#ship-with-confidence)

### [Verified commit-by-commit against Google's colossal monorepo](https://cacm.acm.org/research/why-google-stores-billions-of-lines-of-code-in-a-single-repository/)

[Every Angular commit is checked against hundreds of thousands of tests in Google's internal code repository, representing countless real-world scenarios.](https://cacm.acm.org/research/why-google-stores-billions-of-lines-of-code-in-a-single-repository/)

[Angular is committed to stability for some of Google’s largest products, including Google Cloud. This commitment ensures changes are well-tested, backwards compatible, and include migration tools whenever possible.](https://cacm.acm.org/research/why-google-stores-billions-of-lines-of-code-in-a-single-repository/)

[Learn about Google's monorepo](https://cacm.acm.org/research/why-google-stores-billions-of-lines-of-code-in-a-single-repository/)

### [Clear support policies and predictable release schedule](reference/releases)

[Angular's predictable, time-based release schedule gives your organization confidence in the stability and backwards compatibility of the framework. Long Term Support (LTS) windows make sure you get critical security fixes when you need them. First-party update tools, guides and automated migration schematics help keep your apps up-to-date with the latest advancements to the framework and the web platform.](reference/releases)

[Versioning & releasing](reference/releases)

## [Works at any scale](#works-at-any-scale)

### [Reach users everywhere with internationalization support](guide/i18n)

[Angular's internationalization features handle message translations and formatting, including support for unicode standard ICU syntax.](guide/i18n)

[Internationalization](guide/i18n)

### [Protect your users with security by default](best-practices/security)

[In collaboration with Google's world-class security engineers, Angular aims to make development safe by default. Built-in security features, including HTML sanitization and trusted type support, help protect your users from common vulnerabilities like cross-site scripting and cross-site request forgery.](best-practices/security)

[Security](best-practices/security)

### [Keep large teams productive with Vite and esbuild](tools/cli/build-system-migration)

[Angular CLI includes a fast, modern build pipeline using Vite and esbuild. Developers report building projects with hundreds of thousands of lines of code in less than a minute.](tools/cli/build-system-migration)

[Vite and esbuild](tools/cli/build-system-migration)

### Proven in some of Google's largest web apps

Large Google products build on top of Angular's architecture and help develop new features that further improve Angular's scalability, from [Google Fonts](https://fonts.google.com/) to [Google Cloud](https://console.cloud.google.com).

## [Open-source first](#open-source-first)

### [Made in the open on GitHub](https://github.com/angular/angular)

[Curious what we’re working on? Every PR and commit is available on our GitHub. Run into an issue or bug? We triage GitHub issues regularly to ensure we’re responsive and engaged with our community, and solving the real world problems you’re facing.](https://github.com/angular/angular)

[Star our GitHub](https://github.com/angular/angular)

### [Built with transparency](roadmap)

[Our team publishes a public roadmap of our current and future work and values your feedback. We publish Request for Comments (RFCs) to collect feedback on larger feature changes and ensure the community voice is heard while shaping the future direction of Angular.](roadmap)

[Read our public roadmap](roadmap)

## [A thriving community](#a-thriving-community)

### [Courses, blogs and resources](https://devlibrary.withgoogle.com/products/angular?sort=added)

[Our community is composed of talented developers, writers, instructors, podcasters, and more. The Google for Developers library is just a sample of the high quality resources available for new and experienced developers to continue developing.](https://devlibrary.withgoogle.com/products/angular?sort=added)

[Check out DevLibrary](https://devlibrary.withgoogle.com/products/angular?sort=added)

### [Open Source](https://github.com/angular/angular/blob/main/CONTRIBUTING.md)

[We are thankful for the open source contributors who make Angular a better framework for everyone. From fixing a typo in the docs, to adding major features, we encourage anyone interested to get started on our GitHub.](https://github.com/angular/angular/blob/main/CONTRIBUTING.md)

[Contribute to Angular](https://github.com/angular/angular/blob/main/CONTRIBUTING.md)

### [Community partnerships](https://developers.google.com/community/experts/directory?specialization=angular)

[Our team partners with individuals, educators, and enterprises to ensure we consistently are supporting developers. Angular Google Developer Experts (GDEs) represent community leaders around the world educating, organizing, and developing with Angular. Enterprise partnerships help ensure that Angular scales well for technology industry leaders.](https://developers.google.com/community/experts/directory?specialization=angular)

[Meet the Angular GDEs](https://developers.google.com/community/experts/directory?specialization=angular)

### Partnering with other Google technologies

Angular partners closely with other Google technologies and teams to improve the web.

Our ongoing partnership with Chrome’s Aurora actively explores improvements to user experience across the web, developing built-in performance optimizations like `NgOptimizedImage` and improvements to Angular’s Core Web Vitals.

We are also working with [Firebase](https://firebase.google.com/), [TensorFlow](https://www.tensorflow.org/), [Flutter](https://flutter.dev/), [Material Design](https://m3.material.io/), and [Google Cloud](https://cloud.google.com/) to ensure we provide meaningful integrations across the developer workflow.

### Join the momentum!

----
url: https://angular.dev/api/common/http/HttpClient
----

```
    
      class HttpClient {
}
    
    
```

@paramhandler`HttpHandler`

@returns`HttpClient`

Sends an [`HttpRequest`](/api/common/http/HttpRequest) and returns a stream of [`HttpEvent`](/api/common/http/HttpEvent)s.

@paramreq`HttpRequest<any>`

@returns`Observable<HttpEvent<R>>`

Constructs a request that interprets the body as an `ArrayBuffer` and returns the response in an `ArrayBuffer`.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<ArrayBuffer>`

Constructs a request that interprets the body as a blob and returns the response as a blob.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<Blob>`

Constructs a request that interprets the body as a text string and returns a string value.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<string>`

Constructs a request that interprets the body as an `ArrayBuffer` and returns the the full event stream.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a request that interprets the body as a `Blob` and returns the full event stream.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Blob>>`

Constructs a request which interprets the body as a text string and returns the full event stream.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<string>>`

Constructs a request which interprets the body as a JavaScript object and returns the full event stream.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<any>>`

Constructs a request which interprets the body as a JavaScript object and returns the full event stream.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<R>>`

Constructs a request which interprets the body as an `ArrayBuffer` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a request which interprets the body as a `Blob` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Blob>>`

Constructs a request which interprets the body as a text stream and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<string>>`

Constructs a request which interprets the body as a JavaScript object and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Object>>`

Constructs a request which interprets the body as a JavaScript object and returns the full [`HttpResponse`](/api/common/http/HttpResponse) with the response body in the requested type.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`{ body?: any; observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<R>>`

Constructs a request which interprets the body as a JavaScript object and returns the full [`HttpResponse`](/api/common/http/HttpResponse) as a JavaScript object.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`({ body?: any; observe?: "body" | undefined; responseType?: "json" | undefined; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<Object>`

Constructs a request which interprets the body as a JavaScript object with the response body of the requested type.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`({ body?: any; observe?: "body" | undefined; responseType?: "json" | undefined; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<R>`

Constructs a request where response type and requested observable are not known statically.

@parammethod`string`

The HTTP method.

@paramurl`string`

The endpoint URL.

@paramoptions`({ body?: any; observe?: "body" | "events" | "response" | undefined; responseType?: "arraybuffer" | "blob" | "json" | "text" | undefined; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<any>`

Constructs a `DELETE` request that interprets the body as an `ArrayBuffer` and returns the response as an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<ArrayBuffer>`

Constructs a `DELETE` request that interprets the body as a `Blob` and returns the response as a `Blob`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<Blob>`

Constructs a `DELETE` request that interprets the body as a text string and returns a string.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<string>`

Constructs a `DELETE` request that interprets the body as an `ArrayBuffer` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a `DELETE` request that interprets the body as a `Blob` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "blob"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Blob>>`

Constructs a `DELETE` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "text"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<string>>`

Constructs a `DELETE` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Object>>`

Constructs a `DELETE`request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<T>>`

Constructs a `DELETE` request that interprets the body as an `ArrayBuffer` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a `DELETE` request that interprets the body as a `Blob` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "blob"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Blob>>`

Constructs a `DELETE` request that interprets the body as a text stream and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "text"; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<string>>`

Constructs a `DELETE` request the interprets the body as a JavaScript object and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Object>>`

Constructs a `DELETE` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; body?: any; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<T>>`

Constructs a `DELETE` request that interprets the body as JSON and returns the response body as an object parsed from JSON.

@paramurl`string`

The endpoint URL.

@paramoptions`({ observe?: "body" | undefined; responseType?: "json" | undefined; body?: any; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<Object>`

Constructs a DELETE request that interprets the body as JSON and returns the response in a given type.

@paramurl`string`

The endpoint URL.

@paramoptions`({ observe?: "body" | undefined; responseType?: "json" | undefined; body?: any; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<T>`

Constructs a `GET` request that interprets the body as an `ArrayBuffer` and returns the response in an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<ArrayBuffer>`

Constructs a `GET` request that interprets the body as a `Blob` and returns the response as a `Blob`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<Blob>`

Constructs a `GET` request that interprets the body as a text string and returns the response as a string value.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<string>`

Constructs a `GET` request that interprets the body as an `ArrayBuffer` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a `GET` request that interprets the body as a `Blob` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Blob>>`

Constructs a `GET` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<string>>`

Constructs a `GET` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Object>>`

Constructs a `GET` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<T>>`

Constructs a `GET` request that interprets the body as an `ArrayBuffer` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a `GET` request that interprets the body as a `Blob` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Blob>>`

Constructs a `GET` request that interprets the body as a text stream and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<string>>`

Constructs a `GET` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Object>>`

Constructs a `GET` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse) with the response body in the requested type.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<T>>`

Constructs a `GET` request that interprets the body as JSON and returns the response body as an object parsed from JSON.

@paramurl`string`

The endpoint URL.

@paramoptions`({ observe?: "body" | undefined; responseType?: "json" | undefined; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<Object>`

Constructs a `GET` request that interprets the body as JSON and returns the response body in a given type.

@paramurl`string`

The endpoint URL.

@paramoptions`({ observe?: "body" | undefined; responseType?: "json" | undefined; } & HttpClientCommonOptions) | undefined`

The HTTP options to send with the request.

@returns`Observable<T>`

Constructs a `HEAD` request that interprets the body as an `ArrayBuffer` and returns the response as an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<ArrayBuffer>`

Constructs a \`HEAD\` request that interprets the body as a \`Blob\` and returns the response as a \`Blob\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<Blob>`

Constructs a \`HEAD\` request that interprets the body as a text string and returns the response as a string value.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<string>`

Constructs a \`HEAD\` request that interprets the body as an \`ArrayBuffer\` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a \`HEAD\` request that interprets the body as a \`Blob\` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Blob>>`

Constructs a \`HEAD\` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<string>>`

Constructs a \`HEAD\` request that interprets the body as JSON and returns the full HTTP event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<Object>>`

Constructs a \`HEAD\` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpEvent<T>>`

Constructs a \`HEAD\` request that interprets the body as an \`ArrayBuffer\` and returns the full HTTP response.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a \`HEAD\` request that interprets the body as a \`Blob\` and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Blob>>`

Constructs a \`HEAD\` request that interprets the body as text stream and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<string>>`

Constructs a \`HEAD\` request that interprets the body as JSON and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<Object>>`

Constructs a \`HEAD\` request that interprets the body as JSON and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

The HTTP options to send with the request.

@returns`Observable<HttpResponse<T>>`

Constructs a \`HEAD\` request that interprets the body as JSON and returns the response body as an object parsed from JSON.

@paramurl`string`

The endpoint URL.

@paramoptions`HttpClientCommonOptions | undefined`

The HTTP options to send with the request.

@returns`Observable<Object>`

Constructs a \`HEAD\` request that interprets the body as JSON and returns the response in a given type.

@paramurl`string`

The endpoint URL.

@paramoptions`HttpClientCommonOptions | undefined`

The HTTP options to send with the request.

@returns`Observable<T>`

Constructs a `JSONP` request for the given URL and name of the callback parameter.

@deprecated

JSONP is deprecated as it can cause XSS vulnerabilities. Use standard HTTP requests instead. Intent to remove in future versions of Angular.

@paramurl`string`

The resource URL.

@paramcallbackParam`string`

The callback function name.

@returns`Observable<Object>`

Constructs a `JSONP` request for the given URL and name of the callback parameter.

@deprecated

JSONP is deprecated as it can cause XSS vulnerabilities. Use standard HTTP requests instead. Intent to remove in future versions of Angular.

@paramurl`string`

The resource URL.

@paramcallbackParam`string`

The callback function name.

You must install a suitable interceptor, such as one provided by [`HttpClientJsonpModule`](/api/common/http/HttpClientJsonpModule). If no such interceptor is reached, then the `JSONP` request can be rejected by the configured backend.

@returns`Observable<T>`

Constructs an `OPTIONS` request that interprets the body as an `ArrayBuffer` and returns the response as an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<ArrayBuffer>`

Constructs an \`OPTIONS\` request that interprets the body as a \`Blob\` and returns the response as a \`Blob\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<Blob>`

Constructs an \`OPTIONS\` request that interprets the body as a text string and returns a string value.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<string>`

Constructs an `OPTIONS` request that interprets the body as an `ArrayBuffer` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs an \`OPTIONS\` request that interprets the body as a \`Blob\` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<Blob>>`

Constructs an \`OPTIONS\` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<string>>`

Constructs an \`OPTIONS\` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<Object>>`

Constructs an \`OPTIONS\` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<T>>`

Constructs an `OPTIONS` request that interprets the body as an `ArrayBuffer` and returns the full HTTP response.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs an \`OPTIONS\` request that interprets the body as a \`Blob\` and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<Blob>>`

Constructs an \`OPTIONS\` request that interprets the body as text stream and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<string>>`

Constructs an \`OPTIONS\` request that interprets the body as JSON and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<Object>>`

Constructs an \`OPTIONS\` request that interprets the body as JSON and returns the full \`HttpResponse\`.

@paramurl`string`

The endpoint URL.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<T>>`

Constructs an \`OPTIONS\` request that interprets the body as JSON and returns the response body as an object parsed from JSON.

@paramurl`string`

The endpoint URL.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options.

@returns`Observable<Object>`

Constructs an \`OPTIONS\` request that interprets the body as JSON and returns the response in a given type.

@paramurl`string`

The endpoint URL.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options.

@returns`Observable<T>`

Constructs a `PATCH` request that interprets the body as an `ArrayBuffer` and returns the response as an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<ArrayBuffer>`

Constructs a \`PATCH\` request that interprets the body as a \`Blob\` and returns the response as a \`Blob\`.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<Blob>`

Constructs a \`PATCH\` request that interprets the body as a text string and returns the response as a string value.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<string>`

Constructs a `PATCH` request that interprets the body as an `ArrayBuffer` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a `PATCH` request that interprets the body as a `Blob` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<Blob>>`

Constructs a `PATCH` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<string>>`

Constructs a `PATCH` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<Object>>`

Constructs a `PATCH` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpEvent<T>>`

Constructs a `PATCH` request that interprets the body as an `ArrayBuffer` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a `PATCH` request that interprets the body as a `Blob` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<Blob>>`

Constructs a `PATCH` request that interprets the body as a text stream and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<string>>`

Constructs a `PATCH` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<Object>>`

Constructs a `PATCH` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<HttpResponse<T>>`

Constructs a \`PATCH\` request that interprets the body as JSON and returns the response body as an object parsed from JSON.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options.

@returns`Observable<Object>`

Constructs a \`PATCH\` request that interprets the body as JSON and returns the response in a given type.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to edit.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options.

@returns`Observable<T>`

Constructs a `POST` request that interprets the body as an `ArrayBuffer` and returns an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options.

@returns`Observable<ArrayBuffer>`

Constructs a \`POST\` request that interprets the body as a \`Blob\` and returns the response as a \`Blob\`.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<Blob>`

Constructs a \`POST\` request that interprets the body as a text string and returns the response as a string value.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<string>`

Constructs a `POST` request that interprets the body as an `ArrayBuffer` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a `POST` request that interprets the body as a `Blob` and returns the response in an observable of the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<Blob>>`

Constructs a `POST` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<string>>`

Constructs a POST request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<Object>>`

Constructs a POST request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<T>>`

Constructs a POST request that interprets the body as an `ArrayBuffer` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a `POST` request that interprets the body as a `Blob` and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<Blob>>`

Constructs a `POST` request that interprets the body as a text stream and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<string>>`

Constructs a `POST` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<Object>>`

Constructs a `POST` request that interprets the body as JSON and returns the full [`HttpResponse`](/api/common/http/HttpResponse).

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<T>>`

Constructs a `POST` request that interprets the body as JSON and returns the response body as an object parsed from JSON.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options

@returns`Observable<Object>`

Constructs a `POST` request that interprets the body as JSON and returns an observable of the response.

@paramurl`string`

The endpoint URL.

@parambody`any`

The content to replace with.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options

@returns`Observable<T>`

Constructs a `PUT` request that interprets the body as an `ArrayBuffer` and returns the response as an `ArrayBuffer`.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe?: "body" | undefined; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<ArrayBuffer>`

Constructs a `PUT` request that interprets the body as a `Blob` and returns the response as a `Blob`.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe?: "body" | undefined; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<Blob>`

Constructs a `PUT` request that interprets the body as a text string and returns the response as a string value.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe?: "body" | undefined; responseType: "text"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<string>`

Constructs a `PUT` request that interprets the body as an `ArrayBuffer` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "events"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<ArrayBuffer>>`

Constructs a `PUT` request that interprets the body as a `Blob` and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "events"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<Blob>>`

Constructs a `PUT` request that interprets the body as a text string and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "events"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<string>>`

Constructs a `PUT` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<Object>>`

Constructs a `PUT` request that interprets the body as JSON and returns the full event stream.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "events"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpEvent<T>>`

Constructs a `PUT` request that interprets the body as an `ArrayBuffer` and returns an observable of the full HTTP response.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "response"; responseType: "arraybuffer"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<ArrayBuffer>>`

Constructs a `PUT` request that interprets the body as a `Blob` and returns the full HTTP response.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "response"; responseType: "blob"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<Blob>>`

Constructs a `PUT` request that interprets the body as a text stream and returns the full HTTP response.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "response"; responseType: "text"; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<string>>`

Constructs a `PUT` request that interprets the body as JSON and returns the full HTTP response.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<Object>>`

Constructs a `PUT` request that interprets the body as an instance of the requested type and returns the full HTTP response.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`{ observe: "response"; responseType?: "json" | undefined; } & HttpClientCommonOptions`

HTTP options

@returns`Observable<HttpResponse<T>>`

Constructs a `PUT` request that interprets the body as JSON and returns an observable of JavaScript object.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options

@returns`Observable<Object>`

Constructs a `PUT` request that interprets the body as an instance of the requested type and returns an observable of the requested type.

@paramurl`string`

The endpoint URL.

@parambody`any`

The resources to add/update.

@paramoptions`HttpClientCommonOptions | undefined`

HTTP options

@returns`Observable<T>`

## [Description](#description)

Performs HTTP requests. This service is available as an injectable class, with methods to perform HTTP requests. Each request method has multiple signatures, and the return type varies based on the signature that is called (mainly the values of `observe` and `responseType`).

Note that the `responseType` *options* value is a String that identifies the single data type of the response. A single overload version of the method handles each response type. The value of `responseType` cannot be a union, as the combined signature could imply.

## [Usage Notes](#usage-notes)

### [HTTP Request Example](#http-request-example)

```
// GET heroes whose name contains search term
searchHeroes(term: string): observable<Hero[]>{

 const params = new HttpParams({fromString: 'name=term'});
   return this.httpClient.request('GET', this.heroesUrl, {responseType:'json', params});
}
```

Alternatively, the parameter string can be used without invoking HttpParams by directly joining to the URL.

```
this.httpClient.request('GET', this.heroesUrl + '?' + 'name=term', {responseType:'json'});
```

### [PATCH Example](#patch-example)

```
// PATCH one of the heroes' name
patchHero (id: number, heroName: string): Observable<{}> {
const url = `${this.heroesUrl}/${id}`;   // PATCH api/heroes/42
 return this.httpClient.patch(url, {name: heroName}, httpOptions)
   .pipe(catchError(this.handleError('patchHero')));
}
```

Jump to details

----
url: https://angular.dev/api/forms/signals/DisabledReason
----

```
    
      interface DisabledReason {}
    
    
```

The field that is disabled.

A user-facing message describing the reason for the disablement.

Jump to details

----
url: https://angular.dev/api/core/InjectorType
----

```
    
      interface InjectorType<T> extends Type<T> {}
    
    
```

A type which has an `InjectorDef` static field.

`InjectorTypes` can be used to configure a `StaticInjector`.

This is an opaque type whose structure is highly version dependent. Do not rely on any properties.

Jump to details

----
url: https://angular.dev/api/common/getLocaleMonthNames
----

```
function getLocaleMonthNames(
  locale: string,
  formStyle: FormStyle,
  width: TranslationWidth,
): readonly string[];
```

An array of localized name strings. For example, `[January, February, ...]` for `en-US`.

Jump to details

----
url: https://angular.dev/api/cdk/testing/ComponentHarness
----

```
    
      abstract class ComponentHarness {}
    
    
```

@paramlocatorFactory`LocatorFactory`

@returns`ComponentHarness`

Gets a `Promise` for the [`TestElement`](/api/cdk/testing/TestElement) representing the host element of the component.

@returns`Promise<TestElement>`

Gets a [`LocatorFactory`](/api/cdk/testing/LocatorFactory) for the document root element. This factory can be used to create locators for elements that a component creates outside of its own root element. (e.g. by appending to document.body).

@returns`LocatorFactory`

Creates an asynchronous locator function that can be used to find a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instance or element under the host element of this [`ComponentHarness`](/api/cdk/testing/ComponentHarness).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await ch.locatorFor(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await ch.locatorFor('div', DivHarness)() // Gets a `TestElement` instance for #d1
await ch.locatorFor('span')()            // Throws because the `Promise` rejects
```

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await ch.locatorForOptional(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await ch.locatorForOptional('div', DivHarness)() // Gets a `TestElement` instance for #d1
await ch.locatorForOptional('span')()            // Gets `null`
```

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
// Gets [DivHarness for #d1, TestElement for #d1, DivHarness for #d2, TestElement for #d2]
await ch.locatorForAll(DivHarness, 'div')()
// Gets [TestElement for #d1, TestElement for #d2]
await ch.locatorForAll('div', '#d1')()
// Gets [DivHarness for #d1, IdIsD1Harness for #d1, DivHarness for #d2]
await ch.locatorForAll(DivHarness, IdIsD1Harness)()
// Gets []
await ch.locatorForAll('span')()
```

----
url: https://angular.dev/api/forms/signals/TransformedValueSignal
----

```
    
      interface TransformedValueSignal<TRaw> extends WritableSignal<TRaw> {}
    
    
```

The current parse errors resulting from the last transformation.

----
url: https://angular.dev/api/router/RouterPreloader
----

```
    
      class RouterPreloader implements OnDestroy {}
    
    
```

@paramrouter`Router`

@paraminjector`EnvironmentInjector`

@parampreloadingStrategy`PreloadingStrategy`

@paramloader`RouterConfigLoader`

@returns`RouterPreloader`

@returns`void`

@returns`Observable<any>`

## [Description](#description)

The preloader optimistically loads all router configurations to make navigations into lazily-loaded sections of the application faster.

The preloader runs in the background. When the router bootstraps, the preloader starts listening to all navigation events. After every such event, the preloader will check if any configurations can be loaded lazily.

If a route is protected by `canLoad` guards, the preloaded will not load it.

Jump to details

----
url: https://angular.dev/errors/NG0507
----

Angular throws this error when it detects that the content generated by server-side rendering (SSR) was altered after the rendering. The process of hydration relies on the content to be untouched after SSR, which also includes whitespaces and comment nodes. Those whitespaces and comment nodes must be retained in the HTML generated by the SSR process. Learn more in the Hydration guide.

## [Debugging the error](/errors/NG0507#debugging-the-error)

Typically this happens in the following cases:

Some CDN providers have a built-in feature to remove whitespaces and comment nodes from HTML as an optimization. Please verify if there is such an option in CDN configuration and turn it off. If you use custom post-processing of HTML generated by SSR (as a build step), make sure that this process doesn't remove whitespaces and comment nodes.

----
url: https://angular.dev/api/core/Signal
----

```
    
      type Signal<T> = (() => T) & {  [SIGNAL]: unknown;}
    
    
```

## [Description](#description)

A reactive value which notifies consumers of any changes.

Signals are functions which returns their current value. To access the current value of a signal, call it.

Ordinary values can be turned into [`Signal`](/api/core/Signal)s with the [`signal`](/api/core/signal) function.

Jump to details

----
url: https://angular.dev/guide/components/inputs
----

**TIP:** This guide assumes you've already read the [Essentials Guide](essentials). Read that first if you're new to Angular. If you're familiar with other web frameworks, input properties are similar to *props*.

When you use a component, you commonly want to pass some data to it. A component specifies the data that it accepts by declaring **inputs**:

```
import {Component, input} from '@angular/core';

@Component(/* ... */)
export class CustomSlider {
  // Declare an input named 'value' with a default value of zero.
  value = input(0);
}
```

This lets you bind to the property in a template:

```
<custom-slider [value]="50" />
```

If an input has a default value, TypeScript infers the type from the default value:

```
@Component(/* ... */)
export class CustomSlider {
  // TypeScript infers that this input is a number, returning InputSignal<number>.
  value = input(0);
}
```

You can explicitly declare a type for the input by specifying a generic parameter to the function.

If an input without a default value is not set, its value is `undefined`:

```
@Component(/* ... */)
export class CustomSlider {
  // Produces an InputSignal<number | undefined> because `value` may not be set.
  value = input<number>();
}
```

**Angular records inputs statically at compile-time**. Inputs cannot be added or removed at run-time.

The `input` function has special meaning to the Angular compiler. **You can exclusively call `input` in component and directive property initializers.**

When extending a component class, **inputs are inherited by the child class.**

**Input names are case-sensitive.**

## [Reading inputs](#reading-inputs)

The `input` function returns an [`InputSignal`](/api/core/InputSignal). You can read the value by calling the signal:

```
import {Component, input, computed} from '@angular/core';

@Component(/* ... */)
export class CustomSlider {
  // Declare an input named 'value' with a default value of zero.
  value = input(0);

  // Create a computed expression that reads the value input
  label = computed(() => `The slider's value is ${this.value()}`);
}
```

Signals created by the `input` function are read-only.

## [Required inputs](#required-inputs)

You can declare that an input is `required` by calling [`input.required`](/api/core/input) instead of `input`:

```
@Component(/* ... */)
export class CustomSlider {
  // Declare a required input named value. Returns an `InputSignal<number>`.
  value = input.required<number>();
}
```

Angular enforces that required inputs *must* be set when the component is used in a template. If you try to use a component without specifying all of its required inputs, Angular reports an error at build-time.

Required inputs do not automatically include `undefined` in the generic parameter of the returned [`InputSignal`](/api/core/InputSignal).

## [Configuring inputs](#configuring-inputs)

The `input` function accepts a config object as a second parameter that lets you change the way that input works.

### [Input transforms](#input-transforms)

You can specify a `transform` function to change the value of an input when it's set by Angular.

```
@Component({
  selector: 'custom-slider',
  /*...*/
})
export class CustomSlider {
  label = input('', {transform: trimString});
}

function trimString(value: string | undefined): string {
  return value?.trim() ?? '';
}
```

```
<custom-slider [label]="systemVolume" />
```

In the example above, whenever the value of `systemVolume` changes, Angular runs `trimString` and sets `label` to the result.

The most common use-case for input transforms is to accept a wider range of value types in templates, often including `null` and `undefined`.

**Input transform function must be statically analyzable at build-time.** You cannot set transform functions conditionally or as the result of an expression evaluation.

**Input transform functions should always be [pure functions](https://en.wikipedia.org/wiki/Pure_function).** Relying on state outside the transform function can lead to unpredictable behavior.

#### [Type checking](#type-checking)

When you specify an input transform, the type of the transform function's parameter determines the types of values that can be set to the input in a template.

```
@Component(/* ... */)
export class CustomSlider {
  widthPx = input('', {transform: appendPx});
}

function appendPx(value: number): string {
  return `${value}px`;
}
```

In the example above, the `widthPx` input accepts a `number` while the [`InputSignal`](/api/core/InputSignal) property returns a `string`.

#### [Built-in transformations](#built-in-transformations)

Angular includes two built-in transform functions for the two most common scenarios: coercing values to boolean and numbers.

```
import {Component, input, booleanAttribute, numberAttribute} from '@angular/core';

@Component(/* ... */)
export class CustomSlider {
  disabled = input(false, {transform: booleanAttribute});
  value = input(0, {transform: numberAttribute});
}
```

[`booleanAttribute`](/api/core/booleanAttribute) imitates the behavior of standard HTML [boolean attributes](https://developer.mozilla.org/docs/Glossary/Boolean/HTML), where the *presence* of the attribute indicates a "true" value. However, Angular's [`booleanAttribute`](/api/core/booleanAttribute) treats the literal string `"false"` as the boolean `false`.

[`numberAttribute`](/api/core/numberAttribute) attempts to parse the given value to a number, producing `NaN` if parsing fails.

### [Input aliases](#input-aliases)

You can specify the `alias` option to change the name of an input in templates.

```
@Component(/* ... */)
export class CustomSlider {
  value = input(0, {alias: 'sliderValue'});
}
```

```
<custom-slider [sliderValue]="50" />
```

This alias does not affect usage of the property in TypeScript code.

While you should generally avoid aliasing inputs for components, this feature can be useful for renaming properties while preserving an alias for the original name or for avoiding collisions with the name of native DOM element properties.

## [Model inputs](#model-inputs)

**Model inputs** are a special type of input that enable a component to propagate new values back to its parent component.

When creating a component, you can define a model input similarly to how you create a standard input.

Both types of input allow someone to bind a value into the property. However, **model inputs allow the component author to write values into the property**. If the property is bound with a two-way binding, the new value propagates to that binding.

```
@Component(/* ... */)
export class CustomSlider {
  // Define a model input named "value".
  value = model(0);

  increment() {
    // Update the model input with a new value, propagating the value to any bindings.
    this.value.update((oldValue) => oldValue + 10);
  }
}

@Component({
  /* ... */
  // Using the two-way binding syntax means that any changes to the slider's
  // value automatically propagate back to the `volume` signal.
  // Note that this binding uses the signal *instance*, not the signal value.
  template: `<custom-slider [(value)]="volume" />`,
})
export class MediaControls {
  // Create a writable signal for the `volume` local state.
  volume = signal(0);
}
```

In the above example, the `CustomSlider` can write values into its `value` model input, which then propagates those values back to the `volume` signal in `MediaControls`. This binding keeps the values of `value` and `volume` in sync. Notice that the binding passes the `volume` signal instance, not the *value* of the signal.

In other respects, model inputs work similarly to standard inputs. You can read the value by calling the signal function, including in [reactive contexts](guide/signals#reactive-contexts) like [`computed`](/api/core/computed) and [`effect`](/api/core/effect).

See [Two-way binding](guide/templates/two-way-binding) for more details on two-way binding in templates.

### [Two-way binding with plain properties](#two-way-binding-with-plain-properties)

You can bind a plain JavaScript property to a model input.

```
@Component({
  /* ... */
  // `value` is a model input.
  // The parenthesis-inside-square-brackets syntax (aka "banana-in-a-box") creates a two-way binding
  template: '<custom-slider [(value)]="volume" />',
})
export class MediaControls {
  protected volume = 0;
}
```

In the example above, the `CustomSlider` can write values into its `value` model input, which then propagates those values back to the `volume` property in `MediaControls`. This binding keeps the values of `value` and `volume` in sync.

### [Implicit `change` events](#implicit-change-events)

When you declare a model input in a component or directive, Angular automatically creates a corresponding [output](guide/components/outputs) for that model. The output's name is the model input's name suffixed with "Change".

```
@Directive(/* ... */)
export class CustomCheckbox {
  // This automatically creates an output named "checkedChange".
  // Can be subscribed to using `(checkedChange)="handler()"` in the template.
  checked = model(false);
}
```

Angular emits this change event whenever you write a new value into the model input by calling its `set` or `update` methods.

See [Custom events with outputs](guide/components/outputs) for more details on outputs.

### [Customizing model inputs](#customizing-model-inputs)

You can mark a model input as [required](#required-inputs) or provide an [alias](#input-aliases) in the same way as a standard input.

Model inputs do not support input transforms.

### [When to use model inputs](#when-to-use-model-inputs)

Use model inputs when you want a component to support two-way binding. This is typically appropriate when a component exists to modify a value based on user interaction. Most commonly, custom form controls, such as a date picker or combobox, should use model inputs for their primary value.

## [Choosing input names](#choosing-input-names)

Avoid choosing input names that collide with properties on DOM elements like HTMLElement. Name collisions introduce confusion about whether the bound property belongs to the component or the DOM element.

Avoid adding prefixes for component inputs like you would with component selectors. Since a given element can only host one component, any custom properties can be assumed to belong to the component.

## [Declaring inputs with the `@Input` decorator](#declaring-inputs-with-the-input-decorator)

**TIP:** While the Angular team recommends using the signal-based `input` function for new projects, the original decorator-based [`@Input`](/api/core/Input) API remains fully supported.

You can alternatively declare component inputs by adding the [`@Input`](/api/core/Input) decorator to a property:

```
@Component(/* ... */)
export class CustomSlider {
  @Input() value = 0;
}
```

Binding to an input is the same in both signal-based and decorator-based inputs:

```
<custom-slider [value]="50" />
```

### [Customizing decorator-based inputs](#customizing-decorator-based-inputs)

The [`@Input`](/api/core/Input) decorator accepts a config object that lets you change the way that input works.

#### [Required inputs](#required-inputs-decorator)

You can specify the `required` option to enforce that a given input must always have a value.

```
@Component(/* ... */)
export class CustomSlider {
  @Input({required: true}) value = 0;
}
```

If you try to use a component without specifying all of its required inputs, Angular reports an error at build-time.

#### [Input transforms](#input-transforms-decorator)

You can specify a `transform` function to change the value of an input when it's set by Angular. This transform function works identically to transform functions for signal-based inputs described above.

```
@Component({
  selector: 'custom-slider',
  ...
})
export class CustomSlider {
  @Input({transform: trimString}) label = '';
}

function trimString(value: string | undefined) {
  return value?.trim() ?? '';
}
```

#### [Input aliases](#input-aliases-decorator)

You can specify the `alias` option to change the name of an input in templates.

```
@Component(/* ... */)
export class CustomSlider {
  @Input({alias: 'sliderValue'}) value = 0;
}
```

```
<custom-slider [sliderValue]="50" />
```

The [`@Input`](/api/core/Input) decorator also accepts the alias as its first parameter in place of the config object.

Input aliases work the same way as for signal-based inputs described above.

### [Inputs with getters and setters](#inputs-with-getters-and-setters)

When using decorator-based inputs, a property implemented with a getter and setter can be an input:

```
export class CustomSlider {
  @Input()
  get value(): number {
    return this.internalValue;
  }

  set value(newValue: number) {
    this.internalValue = newValue;
  }

  private internalValue = 0;
}
```

You can even create a *write-only* input by only defining a public setter:

```
export class CustomSlider {
  @Input()
  set value(newValue: number) {
    this.internalValue = newValue;
  }

  private internalValue = 0;
}
```

**Prefer using input transforms instead of getters and setters** if possible.

Avoid complex or costly getters and setters. Angular may invoke an input's setter multiple times, which may negatively impact application performance if the setter performs any costly behaviors, such as DOM manipulation.

## [Specify inputs in the `@Component` decorator](#specify-inputs-in-the-component-decorator)

In addition to the [`@Input`](/api/core/Input) decorator, you can also specify a component's inputs with the `inputs` property in the [`@Component`](/api/core/Component) decorator. This can be useful when a component inherits a property from a base class:

```
// `CustomSlider` inherits the `disabled` property from `BaseSlider`.
@Component({
  ...,
  inputs: ['disabled'],
})
export class CustomSlider extends BaseSlider { }
```

You can additionally specify an input alias in the `inputs` list by putting the alias after a colon in the string:

```
// `CustomSlider` inherits the `disabled` property from `BaseSlider`.
@Component({
  ...,
  inputs: ['disabled: sliderDisabled'],
})
export class CustomSlider extends BaseSlider { }
```

----
url: https://angular.dev/guide/templates/whitespace
----

By default, Angular templates do not preserve whitespace that the framework considers unnecessary. This commonly occurs in two situations: whitespace between elements, and collapsible whitespace inside of text.

## [Whitespace between elements](#whitespace-between-elements)

Most developers prefer to format their templates with newlines and indentation to make the template readable:

```
<section>
  <h3>User profile</h3>
  <label>
    User name
    <input />
  </label>
</section>
```

This template contains whitespace between all of the elements. The following snippet shows the same HTML with each whitespace character replaced with the hash (`#`) character to highlight how much whitespace is present:

```
<!-- Total Whitespace: 20 -->
<section>###<h3>User profile</h3>###<label>#####User name#####<input>###</label>#</section>
```

Preserving the whitespace as written in the template would result in many unnecessary [text nodes](https://developer.mozilla.org/en-US/docs/Web/API/Text) and increase page rendering overhead. By ignoring this whitespace between elements, Angular performs less work when rendering the template on the page, improving overall performance.

## [Collapsible whitespace inside text](#collapsible-whitespace-inside-text)

When your web browser renders HTML on a page, it collapses multiple consecutive whitespace characters to a single character:

```
<!-- What it looks like in the template -->
<p>Hello         world</p>
```

In this example, the browser displays only a single space between "Hello" and "world".

```
<!-- What shows up in the browser -->
<p>Hello world</p>
```

See [How whitespace is handled by HTML, CSS, and in the DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace) for more context on how this works.

Angular avoids sending these unnecessary whitespace characters to the browser in the first place by collapsing them to a single character when it compiles the template.

## [Preserving whitespace](#preserving-whitespace)

You can tell Angular to preserve whitespace in a template by specifying `preserveWhitespaces: true` in the [`@Component`](/api/core/Component) decorator for a template.

```
@Component({
  /* ... */,
  preserveWhitespaces: true,
  template: `
    <p>Hello         world</p>
  `
})
```

Avoid setting this option unless absolutely necessary. Preserving whitespace can cause Angular to produce significantly more nodes while rendering, slowing down your application.

You can additionally use a special HTML entity unique to Angular, `&ngsp;`. This entity produces a single space character that's preserved in the compiled output.

----
url: https://angular.dev/api/common/NgForOf
----

Use the `@for` block instead. Intent to remove in a future major release

## [API](#api)

```
    
      class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {}
    
    
```

```
<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
```

The shorthand form expands into a long form that uses the `ngForOf` selector on an `<ng-template>` element. The content of the `<ng-template>` element is the `<li>` element that held the short-form directive.

Here is the expanded version of the short-form example.

```
<ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
  <li>...</li>
</ng-template>
```

```
<li *ngFor="let user of users; index as i; first as isFirst">
   {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>
```

----
url: https://angular.dev/api/core/NgModule
----

```
@NgModule({
  providers?: (Provider | EnvironmentProviders)[] | undefined;
  declarations?: (any[] | Type<any>)[] | undefined;
  imports?: (any[] | Type<any> | ModuleWithProviders<{}>)[] | undefined;
  exports?: (any[] | Type<any>)[] | undefined;
  bootstrap?: (any[] | Type<any>)[] | undefined;
  schemas?: (any[] | SchemaMetadata)[] | undefined;
  id?: string | undefined;
  jit?: true | undefined;
})
```

The set of injectable objects that are available in the injector of this module.

The set of components, directives, and pipes (declarables that belong to this module.

The set of NgModules whose exported declarables are available to templates in this module.

The set of components, directives, and pipes declared in this NgModule that can be used in the template of any component that is part of an NgModule that imports this NgModule. Exported declarations are the module's public API.

A declarable belongs to one and only one NgModule. A module can list another module among its exports, in which case all of that module's public declaration are exported.

The set of components that are bootstrapped when this module is bootstrapped.

The set of schemas that declare elements to be allowed in the NgModule. Elements and properties that are neither Angular components nor directives must be declared in a schema.

Allowed value are [`NO_ERRORS_SCHEMA`](/api/core/NO_ERRORS_SCHEMA) and [`CUSTOM_ELEMENTS_SCHEMA`](/api/core/CUSTOM_ELEMENTS_SCHEMA).

A name or path that uniquely identifies this NgModule in [`getNgModuleById`](/api/core/getNgModuleById). If left `undefined`, the NgModule is not registered with [`getNgModuleById`](/api/core/getNgModuleById).

When present, this module is ignored by the AOT compiler. It remains in distributed code, and the JIT compiler attempts to compile it at run time, in the browser. To ensure the correct behavior, the app must import `@angular/compiler`.

Jump to details

----
url: https://angular.dev/api/core/booleanAttribute
----

```
function booleanAttribute(value: unknown): boolean;
```

@paramvalue`unknown`

Value to be transformed.

@returns`boolean`

## [Usage Notes](#usage-notes)

```
status = input({ transform: booleanAttribute });
```

Jump to details

----
url: https://angular.dev/api/core/testing/TestBedStatic
----

```
    
      interface TestBedStatic extends TestBed {
}
    
    
```

----
url: https://angular.dev/api/router/OutletContext
----

```
    
      class OutletContext {}
    
    
```

@paramrootInjector`EnvironmentInjector`

@returns`OutletContext`

Jump to details

----
url: https://angular.dev/tutorials/first-app/05-inputs
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/04-interfaces)[](/tutorials/first-app/06-property-binding)

[](/tutorials/first-app/04-interfaces)[](/tutorials/first-app/06-property-binding)

This tutorial lesson demonstrates how to create a component `input` and use it to pass data to a component for customization.

[](https://www.youtube.com/watch?v=eM3zi_n7lNs\&autoplay=1)

**NOTE:** This video reflects an older syntax, but the main concepts remain valid.

## [What you'll learn](#what-youll-learn)

Your app's `HousingLocation` template has a `HousingLocation` property to receive input.

## [Conceptual preview of Inputs](#conceptual-preview-of-inputs)

[Inputs](api/core/input) allow components to specify data that can be passed to it from a parent component.

In this lesson, you'll define an `input` property in the `HousingLocation` component that enables you to customize the data displayed in the component.

Learn more in the [Accepting data with input properties](guide/components/inputs) and [Custom events with outputs](guide/components/outputs) guides.

1. ### [Import the input() function](#import-the-input-function)

   In the code editor, import the `input` helper method from `@angular/core` and the `HousingLocationInfo` interface from `../housinglocation` into the `HousingLocation` component.

   ### Import input and HousingLocationInfo in housing-location.ts

   ```
   import {Component, input} from '@angular/core';
   import {HousingLocationInfo} from '../housinglocation';

   @Component({
     selector: 'app-housing-location',
     template: ` <p>housing-location works!</p> `,
     styleUrls: ['./housing-location.css'],
   })
   export class HousingLocation {
     housingLocation = input.required<HousingLocationInfo>();
   }
   ```

2. ### [Add the Input property](#add-the-input-property)

   Add a required property called `housingLocation` and initialize it using [`input.required()`](/api/core/input) with the type `HousingLocationInfo`.

   ### Declare the input property in housing-location.ts

   ```
   import {Component, input} from '@angular/core';
   import {HousingLocationInfo} from '../housinglocation';

   @Component({
     selector: 'app-housing-location',
     template: ` <p>housing-location works!</p> `,
     styleUrls: ['./housing-location.css'],
   })
   export class HousingLocation {
     housingLocation = input.required<HousingLocationInfo>();
   }
   ```

   You have to invoke the `required` method on `input` to indicate that the parent component must provide a value. In our example application, we know this value will always be passed in — this is by design. The `.required()` call ensures that the TypeScript compiler enforces this and treats the property as non-nullable when this component is used in a template.

3. ### [Pass data to the input](#pass-data-to-the-input)

   Send the `housingLocation` value from the `Home` component to the `housingLocation` property of the HousingLocation component.

   ### Declare the input property for HousingLocation in home.ts

   ```
   import {Component} from '@angular/core';
   import {HousingLocation} from '../housing-location/housing-location';
   import {HousingLocationInfo} from '../housinglocation';

   @Component({
     selector: 'app-home',
     imports: [HousingLocation],
     template: `
       <section>
         <form>
           <input type="text" placeholder="Filter by city" />
           <button class="primary" type="button">Search</button>
         </form>
       </section>
       <section class="results">
         <app-housing-location [housingLocation]="housingLocation" />
       </section>
     `,
     styleUrls: ['./home.css'],
   })
   export class Home {
     readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

     housingLocation: HousingLocationInfo = {
       id: 9999,
       name: 'Test Home',
       city: 'Test city',
       state: 'ST',
       photo: `${this.baseUrl}/example-house.jpg`,
       availableUnits: 99,
       wifi: true,
       laundry: false,
     };
   }
   ```

**SUMMARY:** In this lesson, you created a new `input` property. You also used the `.required` method to ensure the signal value is always defined.

----
url: https://angular.dev/api/animations/AnimationMetadata
----

```
    
      interface AnimationMetadata {}
    
    
```

Jump to details

----
url: https://angular.dev/api/cdk/testing/testbed/Fixture
----

```
    
      type Fixture<T = unknown> = ComponentFixture<T> | DirectiveFixture<T>
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/NgForm
----

```
    
      class NgForm extends ControlContainer implements Form ,AfterViewInit {}
    
    
```

@paramvalidators`(ValidatorFn | Validator)[]`

@paramasyncValidators`(AsyncValidatorFn | AsyncValidator)[]`

@paramcallSetDisabledState`SetDisabledStateOption | undefined`

@returns`NgForm`

Returns whether the form submission has been triggered.

The [`FormGroup`](/api/forms/FormGroup) instance created for this form.

Event emitter for the "ngSubmit" event

Tracks options for the [`NgForm`](/api/forms/NgForm) instance.

**updateOn**: Sets the default `updateOn` value for all child `NgModels` below it unless explicitly set by a child [`NgModel`](/api/forms/NgModel) using `ngModelOptions`). Defaults to 'change'. Possible values: `'change'` | `'blur'` | `'submit'`.

The directive instance.

The internal [`FormGroup`](/api/forms/FormGroup) instance.

Returns an array representing the path to this group. Because this directive always lives at the top level of a form, it is always an empty array.

Returns a map of the controls in this group.

Method that sets up the control directive in this group, re-calculates its value and validity, and adds the instance to the internal list of directives.

@paramdir`NgModel`

The [`NgModel`](/api/forms/NgModel) directive instance.

@returns`void`

Retrieves the [`FormControl`](/api/forms/FormControl) instance from the provided [`NgModel`](/api/forms/NgModel) directive.

@paramdir`NgModel`

The [`NgModel`](/api/forms/NgModel) directive instance.

@returns`FormControl<any>`

Removes the [`NgModel`](/api/forms/NgModel) instance from the internal list of directives

@paramdir`NgModel`

The [`NgModel`](/api/forms/NgModel) directive instance.

@returns`void`

Adds a new [`NgModelGroup`](/api/forms/NgModelGroup) directive instance to the form.

@paramdir`NgModelGroup`

The [`NgModelGroup`](/api/forms/NgModelGroup) directive instance.

@returns`void`

Removes the [`NgModelGroup`](/api/forms/NgModelGroup) directive instance from the form.

@paramdir`NgModelGroup`

The [`NgModelGroup`](/api/forms/NgModelGroup) directive instance.

@returns`void`

Retrieves the [`FormGroup`](/api/forms/FormGroup) for a provided [`NgModelGroup`](/api/forms/NgModelGroup) directive instance

@paramdir`NgModelGroup`

The [`NgModelGroup`](/api/forms/NgModelGroup) directive instance.

@returns`FormGroup<any>`

Sets the new value for the provided [`NgControl`](/api/forms/NgControl) directive.

@paramdir`NgControl`

The [`NgControl`](/api/forms/NgControl) directive instance.

@paramvalue`any`

The new value for the directive's control.

@returns`void`

Sets the value for this [`FormGroup`](/api/forms/FormGroup).

@paramvalue`{ [key: string]: any; }`

The new value

@returns`void`

Method called when the "submit" event is triggered on the form. Triggers the `ngSubmit` emitter to emit the "submit" event as its payload.

@param$event`Event`

The "submit" event object

@returns`boolean`

Method called when the "reset" event is triggered on the form.

@returns`void`

Resets the form to an initial value and resets its submitted status.

@paramvalue`any`

The new value for the form.

@returns`void`

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

## [Description](#description)

Creates a top-level [`FormGroup`](/api/forms/FormGroup) instance and binds it to a form to track aggregate form value and validation status.

As soon as you import the [`FormsModule`](/api/forms/FormsModule), this directive becomes active by default on all `<form>` tags. You don't need to add a special selector.

You optionally export the directive into a local template variable using `ngForm` as the key (ex: `#myForm="ngForm"`). This is optional, but useful. Many properties from the underlying [`FormGroup`](/api/forms/FormGroup) instance are duplicated on the directive itself, so a reference to it gives you access to the aggregate value and validity status of the form, as well as user interaction properties like `dirty` and `touched`.

To register child controls with the form, use [`NgModel`](/api/forms/NgModel) with a `name` attribute. You may use [`NgModelGroup`](/api/forms/NgModelGroup) to create sub-groups within the form.

If necessary, listen to the directive's `ngSubmit` event to be notified when the user has triggered a form submission. The `ngSubmit` event emits the original form submission event.

In template driven forms, all `<form>` tags are automatically tagged as [`NgForm`](/api/forms/NgForm). To import the [`FormsModule`](/api/forms/FormsModule) but skip its usage in some forms, for example, to use native HTML5 validation, add the `ngNoForm` and the `<form>` tags won't create an [`NgForm`](/api/forms/NgForm) directive. In reactive forms, using `ngNoForm` is unnecessary because the `<form>` tags are inert. In that case, you would refrain from using the `formGroup` directive.

## [Exported by](#exported-by)

* `FormsModule`

## [Usage Notes](#usage-notes)

### [Listening for form submission](#listening-for-form-submission)

The following example shows how to capture the form values from the "ngSubmit" event.

```
import {Component} from '@angular/core';
import {NgForm} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
      <input name="first" ngModel required #first="ngModel" />
      <input name="last" ngModel />
      <button>Submit</button>
    </form>

    <p>First name value: {{ first.value }}</p>
    <p>First name valid: {{ first.valid }}</p>
    <p>Form value: {{ f.value | json }}</p>
    <p>Form valid: {{ f.valid }}</p>
  `,
  standalone: false,
})
export class SimpleFormComp {
  onSubmit(f: NgForm) {
    console.log(f.value); // { first: '', last: '' }
    console.log(f.valid); // false
  }
}
```

### [Setting the update options](#setting-the-update-options)

The following example shows you how to change the "updateOn" option from its default using ngFormOptions.

```
<form [ngFormOptions]="{updateOn: 'blur'}">
   <input name="one" ngModel>  <!-- this ngModel will update on blur -->
</form>
```

### [Native DOM validation UI](#native-dom-validation-ui)

In order to prevent the native DOM form validation UI from interfering with Angular's form validation, Angular automatically adds the `novalidate` attribute on any `<form>` whenever `FormModule` or `ReactiveFormModule` are imported into the application. If you want to explicitly enable native DOM validation UI with Angular forms, you can add the `ngNativeValidate` attribute to the `<form>` element:

```
<form ngNativeValidate>
  ...
</form>
```

Jump to details

----
url: https://angular.dev/api/animations/stagger
----

```
function stagger(
  timings: string | number,
  animation: AnimationMetadata | AnimationMetadata[],
): AnimationStaggerMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramtimings`string | number`

A delay value.

@paramanimation`AnimationMetadata | AnimationMetadata[]`

One ore more animation steps.

@returns`AnimationStaggerMetadata`

An object that encapsulates the stagger data.

## [Usage Notes](#usage-notes)

In the following example, a container element wraps a list of items stamped out by an `@for` block. The container element contains an animation trigger that will later be set to query for each of the inner items.

Each time items are added, the opacity fade-in animation runs, and each removed item is faded out. When either of these animations occur, the stagger effect is applied after each item's animation is started.

```
<!-- list.component.html -->
<button (click)="toggle()">Show / Hide Items</button>
<hr />
<div [@listAnimation]="items.length">
  @for(item of items; track $index) {
     <div>{{ item }}</div>
  }
</div>
```

Here is the component code:

```
import {trigger, transition, style, animate, query, stagger} from '@angular/animations';
@Component({
  templateUrl: 'list.component.html',
  animations: [
    trigger('listAnimation', [
    ...
    ])
  ]
})
class ListComponent {
  items = [];

  showItems() {
    this.items = [0,1,2,3,4];
  }

  hideItems() {
    this.items = [];
  }

  toggle() {
    this.items.length ? this.hideItems() : this.showItems();
   }
 }
```

Here is the animation trigger code:

```
trigger('listAnimation', [
  transition('* => *', [ // each time the binding value changes
    query(':leave', [
      stagger(100, [
        animate('0.5s', style({ opacity: 0 }))
      ])
    ]),
    query(':enter', [
      style({ opacity: 0 }),
      stagger(100, [
        animate('0.5s', style({ opacity: 1 }))
      ])
    ])
  ])
])
```

Jump to details

----
url: https://angular.dev/api/forms/AbstractFormGroupDirective
----

```
    
      class AbstractFormGroupDirective extends ControlContainer implements OnInit ,OnDestroy {}
    
    
```

The [`FormGroup`](/api/forms/FormGroup) bound to this directive.

The path to this group from the top-level directive.

The top-level directive for this group if present, otherwise null.

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CDK_DRAG_HANDLE
----

```
    
      const CDK_DRAG_HANDLE: any;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/AfterViewChecked
----

```
    
      interface AfterViewChecked {}
    
    
```

A callback method that is invoked immediately after the default change detector has completed one change-check cycle for a component's view.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define its own after-check functionality.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements AfterViewChecked {
        ngAfterViewChecked() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/api/router/ResolveStart
----

```
    
      class ResolveStart extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramurlAfterRedirects`string`

@paramstate`RouterStateSnapshot`

@returns`ResolveStart`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

## [Description](#description)

An event triggered at the start of the Resolve phase of routing.

Runs in the "resolve" phase whether or not there is anything to resolve. In future, may change to only run when there are things to be resolved.

Jump to details

----
url: https://angular.dev/api/router/NavigationSkippedCode
----

```
    
      enum NavigationSkippedCode {}
    
    
```

A navigation was skipped because the navigation URL was the same as the current Router URL.

A navigation was skipped because the configured [`UrlHandlingStrategy`](/api/router/UrlHandlingStrategy) return `false` for both the current Router URL and the target of the navigation.

Jump to details

----
url: https://angular.dev/tools/devtools/component
----

## [Debug your application](#debug-your-application)

The **Components** tab lets you explore the structure of your application. You can visualize the component and directive instances in the DOM and inspect or modify their state.

### [Explore the application structure](#explore-the-application-structure)

The component tree displays a hierarchical relationship of the *components and directives* within your application.

Click the individual components or directives in the component explorer to select them and preview their properties. Angular DevTools displays properties and metadata on the right side of the component tree.

To look up a component or directive by name use the search box above the component tree.

### [Navigate to the host node](#navigate-to-the-host-node)

To go to the host element of a particular component or directive, double-click it in the component explorer. Angular DevTools will open the Elements tab in Chrome or the Inspector tab in Firefox, and select the associated DOM node.

### [Navigate to source](#navigate-to-source)

For components, Angular DevTools lets you navigate to the component definition in the Sources tab (Chrome) and Debugger tab (Firefox). After you select a particular component, click the icon at the top-right of the properties view:

### [Update property value](#update-property-value)

Like browsers' DevTools, the properties view lets you edit the value of an input, output, or other properties. Right-click on the property value and if edit functionality is available for this value type, a text input will appear. Type the new value and press `Enter` to apply this value to the property.

### [Access selected component or directive in console](#access-selected-component-or-directive-in-console)

As a shortcut in the console, Angular DevTools provides access to instances of recently selected components or directives. Type `$ng0` to get a reference to the instance of the currently selected component or directive, and type `$ng1` for the previously selected instance, `$ng2` for the instance selected before that, and so on.

### [Select a directive or component](#select-a-directive-or-component)

Similar to browsers' DevTools, you can inspect the page to select a particular component or directive. Click the ***Inspect element*** icon in the top left corner within Angular DevTools and hover over a DOM element on the page. The extension recognizes the associated directives and/or components and lets you select the corresponding element in the Component tree.

### [Inspect Deferrable views](#inspect-deferrable-views)

Alongside the directives, the directive tree also includes [`@defer` blocks](/guide/templates/defer).

Clicking a defer block shows more details in the properties sidebar: the different optional blocks (for example `@loading`, `@placeholder`, and `@error`), the configured triggers (defer triggers, prefetch triggers, and hydrate triggers), and timing options such as the `minimum` and `after` values.

### [Hydration](#hydration)

When [hydration](/guide/hydration) is enabled on your SSR/SSG application, the directive tree shows the hydration status of each component.

In case of errors, an error message is displayed on the affected component.

The hydration status can also be visualized on the application itself by enabling the overlay.

Here is an illustration of the hydration overlays on a Angular e-shop example app.

### [Debug the signal graph](#debug-the-signal-graph)

To visualize how the signals, computeds, and effects of a component depend on each other, see [Debug the signal graph](tools/devtools/signals).

----
url: https://angular.dev/api/common/formatNumber
----

```
function formatNumber(
  value: number,
  locale: string,
  digitsInfo?: string | undefined,
): string;
```

The formatted text string.

## [Description](#description)

Formats a number as text, with group sizing, separator, and other parameters based on the locale.

## [Exported by](#exported-by)

* `CommonModule`

Jump to details

----
url: https://angular.dev/api/core/DoBootstrap
----

```
    
      interface DoBootstrap {}
    
    
```

@paramappRef`ApplicationRef`

@returns`void`

## [Description](#description)

Hook for manual bootstrapping of the application instead of using `bootstrap` array in @NgModule annotation. This hook is invoked only when the `bootstrap` array is empty or not provided.

Reference to the current application is provided as a parameter.

See ["Bootstrapping"](/guide/ngmodules/overview#bootstrapping-an-application).

## [Usage Notes](#usage-notes)

The example below uses [`ApplicationRef.bootstrap()`](/api/core/ApplicationRef#bootstrap) to render the `AppComponent` on the page.

```
class AppModule implements DoBootstrap {
  ngDoBootstrap(appRef: ApplicationRef) {
    appRef.bootstrap(AppComponent); // Or some other component
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/TransferState
----

```
    
      class TransferState {}
    
    
```

Get the value corresponding to a key. Return `defaultValue` if key is not found.

@paramkey`StateKey<T>`

@paramdefaultValue`T`

@returns`T`

Set the value corresponding to a key.

@paramkey`StateKey<T>`

@paramvalue`T`

@returns`void`

Remove a key from the store.

@paramkey`StateKey<T>`

@returns`void`

Test whether a key exists in the store.

@paramkey`StateKey<T>`

@returns`boolean`

Indicates whether the state is empty.

Register a callback to provide the value for a key when `toJson` is called.

@paramkey`StateKey<T>`

@paramcallback`() => T`

@returns`void`

Serialize the current state of the store to JSON.

@returns`string`

## [Description](#description)

A key value store that is transferred from the application on the server side to the application on the client side.

The [`TransferState`](/api/core/TransferState) is available as an injectable token. On the client, just inject this token using DI and use it, it will be lazily initialized.

The values in the store are serialized/deserialized using JSON.stringify/JSON.parse. So only boolean, number, string, null and non-class objects will be serialized and deserialized in a non-lossy manner.

Jump to details

----
url: https://angular.dev/api/router/UrlMatchResult
----

```
    
      type UrlMatchResult = {  consumed: UrlSegment[];  posParams?: {[name: string]: UrlSegment};}
    
    
```

## [Description](#description)

Represents the result of matching URLs with a custom matching function.

* `consumed` is an array of the consumed URL segments.
* `posParams` is a map of positional parameters.

Jump to details

----
url: https://angular.dev/guide/testing/pipes
----

You can test [pipes](guide/templates/pipes) without the Angular testing utilities.

## [Testing the `TitleCasePipe`](#testing-the-titlecasepipe)

A pipe class has one method, `transform`, that manipulates the input value into a transformed output value. The `transform` implementation rarely interacts with the DOM. Most pipes have no dependence on Angular other than the [`@Pipe`](/api/core/Pipe) metadata and an interface.

Consider a [`TitleCasePipe`](/api/common/TitleCasePipe) that capitalizes the first letter of each word. Here's an implementation with a regular expression.

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({name: 'titlecase', pure: true})
/** Transform to Title Case: uppercase the first letter of the words in a string. */
export class TitleCasePipe implements PipeTransform {
  transform(input: string): string {
    return input.length === 0
      ? ''
      : input.replace(/\w\S*/g, (txt) => txt[0].toUpperCase() + txt.slice(1).toLowerCase());
  }
}
```

Anything that uses a regular expression is worth testing thoroughly. You can use standard unit testing techniques to explore the expected cases and the edge cases.

```
describe('TitleCasePipe', () => {
  // This pipe is a pure, stateless function so no need for BeforeEach
  const pipe = new TitleCasePipe();

  it('transforms "abc" to "Abc"', () => {
    expect(pipe.transform('abc')).toBe('Abc');
  });

  it('transforms "abc def" to "Abc Def"', () => {
    expect(pipe.transform('abc def')).toBe('Abc Def');
  });

  // ... more tests ...
});
```

## [Writing DOM tests to support a pipe test](#writing-dom-tests-to-support-a-pipe-test)

These are tests of the pipe *in isolation*. They can't tell if the [`TitleCasePipe`](/api/common/TitleCasePipe) is working properly as applied in the application components.

Consider adding component tests such as this one:

```
it('should convert hero name to Title Case', async () => {
  // get the name's input and display elements from the DOM
  const hostElement: HTMLElement = harness.routeNativeElement!;
  const nameInput: HTMLInputElement = hostElement.querySelector('input')!;
  const nameDisplay: HTMLElement = hostElement.querySelector('span')!;

  // simulate user entering a new name into the input box
  nameInput.value = 'quick BROWN  fOx';

  // Dispatch a DOM event so that Angular learns of input value change.
  nameInput.dispatchEvent(new Event('input'));

  // Wait for Angular to update the display binding through the title pipe
  await harness.fixture.whenStable();

  expect(nameDisplay.textContent).toBe('Quick Brown  Fox');
});
```

----
url: https://angular.dev/api/aria/toolbar/ToolbarWidgetGroup
----

```
    
      class ToolbarWidgetGroup {}
    
    
```

A reference to the host element.

Whether the widget group is disabled.

Jump to details

----
url: https://angular.dev/best-practices/zone-pollution
----

**Zone.js** is a signaling mechanism that Angular uses to detect when an application state might have changed. It captures asynchronous operations like `setTimeout`, network requests, and event listeners. Angular schedules change detection based on signals from Zone.js.

In some cases scheduled [tasks](https://developer.mozilla.org/docs/Web/API/HTML_DOM_API/Microtask_guide#tasks) or [microtasks](https://developer.mozilla.org/docs/Web/API/HTML_DOM_API/Microtask_guide#microtasks) don’t make any changes in the data model, which makes running change detection unnecessary. Common examples are:

* `requestAnimationFrame`, `setTimeout` or `setInterval`
* Task or microtask scheduling by third-party libraries

This section covers how to identify such conditions, and how to run code outside the Angular zone to avoid unnecessary change detection calls.

## [Identifying unnecessary change detection calls](#identifying-unnecessary-change-detection-calls)

You can detect unnecessary change detection calls using Angular DevTools. Often they appear as consecutive bars in the profiler’s timeline with source `setTimeout`, `setInterval`, `requestAnimationFrame`, or an event handler. When you have limited calls within your application of these APIs, the change detection invocation is usually caused by a third-party library.

In the image above, there is a series of change detection calls triggered by event handlers associated with an element. That’s a common challenge when using third-party, non-native Angular components, which do not alter the default behavior of [`NgZone`](/api/core/NgZone).

## [Run tasks outside `NgZone`](#run-tasks-outside-ngzone)

In such cases, you can instruct Angular to avoid calling change detection for tasks scheduled by a given piece of code using [NgZone](/api/core/NgZone).

### Run outside of the Zone

```
1import { Component, NgZone, OnInit, inject } from '@angular/core';
2
3@Component(...)
4class App implements OnInit {
5  private ngZone = inject(NgZone);
6
7  ngOnInit() {
8    this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates, 500));
9  }
10}
```

The preceding snippet instructs Angular to call `setInterval` outside the Angular Zone and skip running change detection after `pollForUpdates` runs.

Third-party libraries commonly trigger unnecessary change detection cycles when their APIs are invoked within the Angular zone. This phenomenon particularly affects libraries that set up event listeners or initiate other tasks (such as timers, XHR requests, etc.). Avoid these extra cycles by calling library APIs outside the Angular zone:

### Move the plot initialization outside of the Zone

```
1import { Component, NgZone, OnInit, inject } from '@angular/core';
2import * as Plotly from 'plotly.js-dist-min';
3
4@Component(...)
5class App implements OnInit {
6  private ngZone = inject(NgZone);
7
8  ngOnInit() {
9    this.ngZone.runOutsideAngular(() => {
10      Plotly.newPlot('chart', data);
11    });
12  }
13}
```

Running `Plotly.newPlot('chart', data);` within `runOutsideAngular` instructs the framework that it shouldn’t run change detection after the execution of tasks scheduled by the initialization logic.

For example, if `Plotly.newPlot('chart', data)` adds event listeners to a DOM element, Angular does not run change detection after the execution of their handlers.

But sometimes, you may need to listen to events dispatched by third-party APIs. In such cases, it's important to remember that those event listeners will also execute outside of the Angular zone if the initialization logic was done there:

### Check whether the handler is called outside of the Zone

```
1import { Component, NgZone, OnInit, output, inject } from '@angular/core';
2import * as Plotly from 'plotly.js-dist-min';
3
4@Component(...)
5class App implements OnInit {
6  private ngZone = inject(NgZone);
7
8  plotlyClick = output<Plotly.PlotMouseEvent>();
9
10  ngOnInit() {
11    this.ngZone.runOutsideAngular(() => {
12      this.createPlotly();
13    });
14  }
15
16  private async createPlotly() {
17    const plotly = await Plotly.newPlot('chart', data);
18
19    plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {
20      // This handler will be called outside of the Angular zone because
21      // the initialization logic is also called outside of the zone. To check
22      // whether we're in the Angular zone, we can call the following:
23      console.log(NgZone.isInAngularZone());
24      this.plotlyClick.emit(event);
25    });
26  }
27}
```

If you need to dispatch events to parent components and execute specific view update logic, you should consider re-entering the Angular zone to instruct the framework to run change detection or run change detection manually:

### Re-enter the Angular zone when dispatching event

```
1import { Component, NgZone, OnInit, output, inject } from '@angular/core';
2import * as Plotly from 'plotly.js-dist-min';
3
4@Component(...)
5class App implements OnInit {
6  private ngZone = inject(NgZone);
7
8  plotlyClick = output<Plotly.PlotMouseEvent>();
9
10  ngOnInit() {
11    this.ngZone.runOutsideAngular(() => {
12      this.createPlotly();
13    });
14  }
15
16  private async createPlotly() {
17    const plotly = await Plotly.newPlot('chart', data);
18
19    plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {
20      this.ngZone.run(() => {
21        this.plotlyClick.emit(event);
22      });
23    });
24  }
25}
```

The scenario of dispatching events outside of the Angular zone may also arise. It's important to remember that triggering change detection (for example, manually) may result in the creation/update of views outside of the Angular zone.

----
url: https://angular.dev/extended-diagnostics/NG8108
----

`ngSkipHydration` is a special attribute which indicates to Angular that a particular component should be opted-out of [hydration](guide/hydration). This diagnostic ensures that this attribute `ngSkipHydration` is set statically and the value is either set to `"true"` or an empty value.

```
import {Component} from '@angular/core';

@Component({
  template: `<user-viewer ngSkipHydration="hasUser" />`,
})
class MyComponent {
  hasUser = true;
}
```

## [What's wrong with that?](#whats-wrong-with-that)

As a special attribute implemented by Angular, `ngSkipHydration` needs to be statically analyzable so Angular knows at compile-time whether or not hydration is needed for a component.

## [What should I do instead?](#what-should-i-do-instead)

When using the `ngSkipHydration`, ensure that it's set as a static attribute (i.e. you do not use the Angular template binding syntax).

```
import {Component} from '@angular/core';

@Component({
  template: `
    <user-viewer ngSkipHydration />
    <user-viewer ngSkipHydration="true" />
  `,
})
class MyComponent {}
```

If a conditional is necessary, you can wrap the component in an `*ngIf`.

```
import {Component} from '@angular/core';

@Component({
  template: `
    <div *ngIf="hasUser; else noUser">
      <user-viewer ngSkipHydration />
    </div>

    <ng-template #noUser>
      <user-viewer />
    </ng-template>
  `,
})
class MyComponent {}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `skipHydrationNotStatic` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "skipHydrationNotStatic": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/core/isSignal
----

```
function isSignal(value: unknown): value is Signal<unknown>;
```

@paramvalue`unknown`

@returns`value is Signal<unknown>`

Jump to details

----
url: https://angular.dev/api/router/RouterFeatures
----

```
    
      type RouterFeatures = | PreloadingFeature  | DebugTracingFeature  | InitialNavigationFeature  | InMemoryScrollingFeature  | RouterConfigurationFeature  | NavigationErrorHandlerFeature  | ComponentInputBindingFeature  | ViewTransitionsFeature  | AutoCleanupInjectorsFeature  | RouterHashLocationFeature  | ExperimentalPlatformNavigationFeature  | RouterResourcesFeature
    
    
```

Jump to details

----
url: https://angular.dev/api/router/DefaultTitleStrategy
----

```
    
      class DefaultTitleStrategy extends TitleStrategy {}
    
    
```

@paramtitle`Title`

@returns`DefaultTitleStrategy`

Sets the title of the browser to the title built from the given router state.

@paramsnapshot`RouterStateSnapshot`

The current router state. The title is taken from the deepest primary route.

Jump to details

----
url: https://angular.dev/api/router/ResourceResult
----

```
    
      type ResourceResult = Record<  string,  Resource<unknown> & Pick<WritableResource<unknown>, 'reload'>>
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG0204
----

This error occurs when Angular cannot resolve a dependency for a class during dependency injection. This most commonly affects classes using constructor injection, where Angular relies on TypeScript metadata to determine parameter types.

The most common causes are:

1. A service class is missing the [`@Service()`](/api/core/Service) or the [`@Injectable`](/api/core/Injectable) decorator
2. An [`InjectionToken`](/api/core/InjectionToken) lacks a proper provider definition
3. A constructor parameter cannot be resolved

**NOTE:** The `inject()` function takes an explicit token, so the "unresolvable parameter" scenario does not apply to it directly. However, if the injected class itself is missing [`@Service()`](/api/core/Service) and has its own constructor dependencies, the error can still occur.

## [Common scenarios](#common-scenarios)

### [Missing `@Service()` decorator](#missing-service-decorator)

When a class has constructor dependencies but lacks the [`@Service()`](/api/core/Service) decorator, Angular cannot resolve its parameters:

### Missing @Service() decorator

```
export class UserClient {
  constructor(private http: HttpClient) {} // Angular can't resolve this
}
```

Add the [`@Service()`](/api/core/Service) decorator to fix this:

```
@Service()
export class UserClient {
  constructor(private http: HttpClient) {}
}
```

### [Unresolvable constructor parameters](#unresolvable-constructor-parameters)

This error also appears when Angular cannot determine the type of a constructor parameter:

```
@Service()
export class DataStore {
  // Angular can't resolve 'config' without a provider
  constructor(private config: AppConfig) {}
}
```

Ensure all constructor parameters either have providers configured or use [`@Optional()`](/api/core/Optional) for optional dependencies.

## [Debugging the error](#debugging-the-error)

The error message includes details about which token could not be resolved:

* `Can't resolve all parameters for X: (?, ?, ?)` — The `?` marks indicate unresolvable parameters. Check that the class has [`@Service`](/api/core/Service) or [`@Injectable()`](/api/core/Injectable) and all dependencies have providers.
* `Token X is missing a ɵprov definition` — An [`InjectionToken`](/api/core/InjectionToken) was used without configuring a provider. Register the token with a value using `{provide: TOKEN, useValue: ...}` or add a default factory to the token definition.

Work backwards from the error's stack trace to identify where the problematic injection occurs, then verify that:

1. The class has a [`@Service`](/api/core/Service) or [`@Injectable()`](/api/core/Injectable) decorator
2. All constructor parameters have registered providers
3. Any [`InjectionToken`](/api/core/InjectionToken) has a configured provider or default value

----
url: https://angular.dev/api/core/testing/flush
----

```
function flush(maxTurns?: number | undefined): number;
```

@parammaxTurns`number | undefined`

The maximum number of times the scheduler attempts to clear its queue before throwing an error.

@returns`number`

The simulated time elapsed, in milliseconds.

## [Description](#description)

**IMPORTANT:** This API requires Zone.js and cannot be used with the Vitest test runner

Flushes any pending microtasks and simulates the asynchronous passage of time for the timers in the [`fakeAsync`](/api/core/testing/fakeAsync) zone by draining the macrotask queue until it is empty.

Jump to details

----
url: https://angular.dev/api/router/CanActivateFn
----

```
    
      type CanActivateFn = (  route: ActivatedRouteSnapshot,  state: RouterStateSnapshot,) => MaybeAsync<GuardResult>
    
    
```

## [Description](#description)

The signature of a function used as a `canActivate` guard on a [`Route`](/api/router/Route).

If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), the current navigation is cancelled and a new navigation begins to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements and uses a [`CanActivateFn`](/api/router/CanActivateFn) that checks whether the current user has permission to activate the requested route.

```
@Injectable()
class UserToken {}

@Injectable()
class PermissionsService {
  canActivate(currentUser: UserToken, userId: string): boolean {
    return true;
  }
  canMatch(currentUser: UserToken): boolean {
    return true;
  }
}

const canActivateTeam: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  return inject(PermissionsService).canActivate(inject(UserToken), route.params['id']);
};
```

Here, the defined guard function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
bootstrapApplication(App, {
   providers: [
     provideRouter([
       {
         path: 'team/:id',
         component: TeamComponent,
         canActivate: [canActivateTeam],
       },
     ]),
   ],
 });
```

Jump to details

----
url: https://angular.dev/api/platform-browser/bootstrapApplication
----

```
function bootstrapApplication(
  rootComponent: Type<unknown>,
  options?: ApplicationConfig | undefined,
  context?: BootstrapContext | undefined,
): Promise<ApplicationRef>;
```

@paramrootComponent`Type<unknown>`

A reference to a standalone component that should be rendered.

@paramoptions`ApplicationConfig | undefined`

Extra configuration for the bootstrap operation, see [`ApplicationConfig`](/api/core/ApplicationConfig) for additional info.

@paramcontext`BootstrapContext | undefined`

Optional context object that can be used to provide a pre-existing platform injector. This is useful for advanced use-cases, for example, server-side rendering, where the platform is created for each request.

@returns`Promise<ApplicationRef>`

A promise that returns an [`ApplicationRef`](/api/core/ApplicationRef) instance once resolved.

## [Usage Notes](#usage-notes)

The root component passed into this function **must** be a standalone one

```
@Component({
  template: 'Hello world!'
})
class Root {}

const appRef: ApplicationRef = await bootstrapApplication(Root);
```

You can add the list of providers that should be available in the application injector by specifying the `providers` field in an object passed as the second argument:

```
await bootstrapApplication(Root, {
  providers: [
    {provide: BACKEND_URL, useValue: 'https://yourdomain.com/api'}
  ]
});
```

The `importProvidersFrom` helper method can be used to collect all providers from any existing NgModule (and transitively from all NgModules that it imports):

```
await bootstrapApplication(Root, {
  providers: [
    importProvidersFrom(SomeNgModule)
  ]
});
```

Note: the [`bootstrapApplication`](/api/platform-browser/bootstrapApplication) method doesn't include [Testability](api/core/Testability) by default. You can add [Testability](api/core/Testability) by getting the list of necessary providers using [`provideProtractorTestingSupport()`](/api/platform-browser/provideProtractorTestingSupport) function and adding them into the `providers` array, for example:

```
import {provideProtractorTestingSupport} from '@angular/platform-browser';

await bootstrapApplication(Root, {providers: [provideProtractorTestingSupport()]});
```

Jump to details

----
url: https://angular.dev/api/service-worker/provideServiceWorker
----

```
function provideServiceWorker(
  script: string,
  options?: SwRegistrationOptions,
): EnvironmentProviders;
```

@paramscript`string`

@paramoptions`SwRegistrationOptions`

@returns`EnvironmentProviders`

Jump to details

----
url: https://angular.dev/api/core/afterRenderEffect
----

```
function afterRenderEffect(
  callback: (onCleanup: EffectCleanupRegisterFn) => void,
  options?: AfterRenderEffectOptions | undefined,
): AfterRenderRef;
function afterRenderEffect<E = never, W = never, M = never>(
  spec: {
    earlyRead?: ((onCleanup: EffectCleanupRegisterFn) => E) | undefined;
    write?:
      | ((
          ...args: [
            ...([E] extends [never] ? [] : [Signal<E>]),
            EffectCleanupRegisterFn,
          ]
        ) => W)
      | undefined;
    mixedReadWrite?:
      | ((
          ...args: [
            ...([W] extends [never]
              ? [E] extends [never]
                ? []
                : [Signal<E>]
              : [Signal<W>]),
            EffectCleanupRegisterFn,
          ]
        ) => M)
      | undefined;
    read?:
      | ((
          ...args: [
            ...([M] extends [never]
              ? [W] extends [never]
                ? [E] extends [never]
                  ? []
                  : [Signal<E>]
                : [Signal<W>]
              : [Signal<M>]),
            EffectCleanupRegisterFn,
          ]
        ) => void)
      | undefined;
  },
  options?: AfterRenderEffectOptions | undefined,
): AfterRenderRef;
```

Register an effect that, when triggered, is invoked when the application finishes rendering, during the `mixedReadWrite` phase.

You should prefer specifying an explicit phase for the effect instead, or you risk significant performance degradation.

Note that callback-based [`afterRenderEffect`](/api/core/afterRenderEffect)s will run

* in the order it they are registered
* only when dirty
* on browser platforms only
* during the `mixedReadWrite` phase

Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs. You must use caution when directly reading or writing the DOM and layout.

@paramcallback`(onCleanup: EffectCleanupRegisterFn) => void`

An effect callback function to register

@paramoptions`AfterRenderEffectOptions | undefined`

Options to control the behavior of the callback

@returns`AfterRenderRef`

Register effects that, when triggered, are invoked when the application finishes rendering, during the specified phases. The available phases are:

* `earlyRead` Use this phase to **read** from the DOM before a subsequent `write` callback, for example to perform custom layout that the browser doesn't natively support. Prefer the `read` phase if reading can wait until after the write phase. **Never** write to the DOM in this phase.
* `write` Use this phase to **write** to the DOM. **Never** read from the DOM in this phase.
* `mixedReadWrite` Use this phase to read from and write to the DOM simultaneously. **Never** use this phase if it is possible to divide the work among the other phases instead.
* `read` Use this phase to **read** from the DOM. **Never** write to the DOM in this phase.

You should prefer using the `read` and `write` phases over the `earlyRead` and `mixedReadWrite` phases when possible, to avoid performance degradation.

Note that:

* Effects run in the following phase order, only when dirty through signal dependencies:

  1. `earlyRead`
  2. `write`
  3. `mixedReadWrite`
  4. `read`

* [`afterRenderEffect`](/api/core/afterRenderEffect)s in the same phase run in the order they are registered.

* [`afterRenderEffect`](/api/core/afterRenderEffect)s run on browser platforms only, they will not run on the server.

* [`afterRenderEffect`](/api/core/afterRenderEffect)s will run at least once.

The first phase callback to run as part of this spec will receive no parameters. Each subsequent phase callback in this spec will receive the return value of the previously run phase callback as a [`Signal`](/api/core/Signal). This can be used to coordinate work across multiple phases.

Angular is unable to verify or enforce that phases are used correctly, and instead relies on each developer to follow the guidelines documented for each value and carefully choose the appropriate one, refactoring their code if necessary. By doing so, Angular is better able to minimize the performance degradation associated with manual DOM access, ensuring the best experience for the end users of your application or library.

Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs. You must use caution when directly reading or writing the DOM and layout.

@paramspec`{ earlyRead?: ((onCleanup: EffectCleanupRegisterFn) => E) | undefined; write?: ((...args: [...[E] extends [never] ? [] : [Signal<E>], EffectCleanupRegisterFn]) => W) | undefined; mixedReadWrite?: ((...args: [...[W] extends [never] ? [E] extends [never] ? [] : [Signal<E>] : [Signal<W>], EffectCleanupRegisterFn]) => M) | undefined; read?: ((...args: [...[M] extends [never] ? [W] extends [never] ? [E] extends [never] ? [] : [Signal<E>] : [Signal<W>] : [Signal<M>], EffectCleanupRegisterFn]) => void) | undefined; }`

The effect functions to register

@paramoptions`AfterRenderEffectOptions | undefined`

Options to control the behavior of the effects

@returns`AfterRenderRef`

Usage notes

Use [`afterRenderEffect`](/api/core/afterRenderEffect) to create effects that will read or write from the DOM and thus should run after rendering.

Jump to details

----
url: https://angular.dev/api/ssr/node/writeResponseToNodeResponse
----

```
function writeResponseToNodeResponse(
  source: Response,
  destination:
    ServerResponse<IncomingMessage> | Http2ServerResponse<Http2ServerRequest>,
): Promise<void>;
```

@paramsource`Response`

* The web-standard `Response` object to stream from.

@paramdestination`ServerResponse<IncomingMessage> | Http2ServerResponse<Http2ServerRequest>`

* The Node.js response object (`ServerResponse` or `Http2ServerResponse`) to stream into.

@returns`Promise<void>`

A promise that resolves once the streaming operation is complete.

## [Description](#description)

Streams a web-standard `Response` into a Node.js `ServerResponse` or `Http2ServerResponse`.

This function adapts the web `Response` object to write its content to a Node.js response object, handling both HTTP/1.1 and HTTP/2.

Jump to details

----
url: https://angular.dev/api/common/DATE_PIPE_DEFAULT_TIMEZONE
----

use DATE\_PIPE\_DEFAULT\_OPTIONS token to configure DatePipe

## [API](#api)

```
    
      const DATE_PIPE_DEFAULT_TIMEZONE: InjectionToken<string>;
    
    
```

Jump to details

----
url: https://angular.dev/essentials/signals
----

# Signals

[](https://github.com/angular/angular/edit/main/adev/src/content/introduction/essentials/signals.md "Edit this page")

Create and manage dynamic data.

In Angular, you use *signals* to create and manage state. A signal is a lightweight wrapper around a value.

Use the [`signal`](/api/core/signal) function to create a signal for holding local state:

```
import {signal} from '@angular/core';

// Create a signal with the `signal` function.
const firstName = signal('Morgan');

// Read a signal value by calling it— signals are functions.
console.log(firstName());

// Change the value of this signal by calling its `set` method with a new value.
firstName.set('Jaime');

// You can also use the `update` method to change the value
// based on the previous value.
firstName.update((name) => name.toUpperCase());
```

Angular tracks where signals are read and when they're updated. The framework uses this information to do additional work, such as updating the DOM with new state. This ability to respond to changing signal values over time is known as *reactivity*.

## [Computed expressions](#computed-expressions)

A [`computed`](/api/core/computed) is a signal that produces its value based on other signals.

```
import {signal, computed} from '@angular/core';

const firstName = signal('Morgan');
const firstNameCapitalized = computed(() => firstName().toUpperCase());

console.log(firstNameCapitalized()); // MORGAN
```

A [`computed`](/api/core/computed) signal is read-only; it does not have a `set` or an `update` method. Instead, the value of the [`computed`](/api/core/computed) signal automatically changes when any of the signals it reads change:

```
import {signal, computed} from '@angular/core';

const firstName = signal('Morgan');
const firstNameCapitalized = computed(() => firstName().toUpperCase());
console.log(firstNameCapitalized()); // MORGAN

firstName.set('Jaime');
console.log(firstNameCapitalized()); // JAIME
```

## [Using signals in components](#using-signals-in-components)

Use [`signal`](/api/core/signal) and [`computed`](/api/core/computed) inside your components to create and manage state:

```
@Component({
  /* ... */
})
export class UserProfile {
  isTrial = signal(false);
  isTrialExpired = signal(false);
  showTrialDuration = computed(() => this.isTrial() && !this.isTrialExpired());

  activateTrial() {
    this.isTrial.set(true);
  }
}
```

**TIP:** Want to know more about Angular Signals? See the [In-depth Signals guide](guide/signals) for the full details.

## [Next Step](#next-step)

Now that you have learned how to declare and manage dynamic data, it's time to learn how to use that data inside of templates.

----
url: https://angular.dev/api/cdk/drag-drop/CdkDragPlaceholder
----

```
    
      class CdkDragPlaceholder<T = any> implements OnDestroy {}
    
    
```

Context data to be added to the placeholder template instance.

@returns`void`

Jump to details

----
url: https://angular.dev/guide/forms/signals/migration
----

This guide provides strategies for migrating existing codebases to Signal Forms, focusing on interoperability with existing Reactive Forms.

## [Top-down migration using `compatForm`](#top-down-migration-using-compatform)

Sometimes you may want to use existing reactive [`FormControl`](/api/forms/FormControl) instances within a Signal Form. This is useful for controls that involve:

* Complex asynchronous logic.
* Intricate RxJS operators that are not yet ported.
* Integration with existing third-party libraries.

### [Integrating a `FormControl` into a signal form](#integrating-a-formcontrol-into-a-signal-form)

Consider an existing `passwordControl` that uses a specialized `enterprisePasswordValidator`. Instead of rewriting the validator, you can bridge the control into your signal state.

We can do it using [`compatForm`](/api/forms/signals/compat/compatForm):

```
import {signal} from '@angular/core';
import {FormControl, Validators} from '@angular/forms';
import {compatForm} from '@angular/forms/signals/compat';

// 1. Existing control with a specialized validator
const passwordControl = new FormControl('', {
  validators: [Validators.required, enterprisePasswordValidator()],
  nonNullable: true,
});

// 2. Wrap it inside your form state signal
const user = signal({
  email: '',
  password: passwordControl, // Nest the existing control directly
});

// 3. Create the form
const f = compatForm(user);

// Access values via the signal tree
console.log(f.email().value()); // Current email value
console.log(f.password().value()); // Current value of passwordControl

// Reactive state is proxied automatically
const isPasswordValid = f.password().valid();
const passwordErrors = f.password().errors(); // Returns CompatValidationError if the existing validator fails
```

In the template, use standard reactive syntax by binding the underlying control:

```
<form novalidate>
  <div>
    <label>
      Email:
      <input [formField]="f.email" />
    </label>
  </div>

  <div>
    <label>
      Password:
      <input [formField]="f.password" type="password" />
    </label>

    @if (f.password().touched() && f.password().invalid()) {
      <div class="error-list">
        @for (error of f.password().errors(); track error) {
          <p>{{ error.message || error.kind }}</p>
        }
      </div>
    }
  </div>
</form>
```

### app.ts

```
import {JsonPipe} from '@angular/common';
import {Component, computed, signal} from '@angular/core';
import {AbstractControl, FormControl, Validators} from '@angular/forms';
import {FormField} from '@angular/forms/signals';
import {compatForm} from '@angular/forms/signals/compat';

// Dummy enterprisePasswordValidator for the example
function enterprisePasswordValidator() {
  return (control: AbstractControl) => {
    if (control.value && control.value.length < 8) {
      return {enterprisePassword: {message: 'Password must be at least 8 characters.'}};
    }
    return null;
  };
}

@Component({
  selector: 'app',
  imports: [FormField, JsonPipe],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  // 1. Existing legacy control with a specialized validator
  readonly passwordControl = new FormControl('', {
    validators: [Validators.required, enterprisePasswordValidator()],
    nonNullable: true,
  });

  // 2. Wrap it inside your form state signal
  readonly user = signal({
    email: '',
    password: this.passwordControl, // Nest the legacy control directly
  });

  // 3. Create the form
  readonly f = compatForm(this.user);

  // We have to manually extract values, because JSON pipe can't serialize FormControl
  readonly formValue = computed(() => ({
    email: this.f.email().value(),
    password: this.f.password().value(),
  }));

  constructor() {
    console.log(this.f.email().value()); // ""
    console.log(this.f.password().value()); // Current value of passwordControl
  }
}
```

### app.html

```
<form>
  <div>
    <label>
      Email:
      <input [formField]="f.email" />
    </label>
  </div>

  <div>
    <label>
      Password:
      <input [formField]="f.password" type="password" />
    </label>

    @if (f.password().touched() && f.password().invalid()) {
      <div class="error-list">
        @for (error of f.password().errors(); track error) {
          <p>{{ error.message || error.kind }}</p>
        }
      </div>
    }
  </div>
  <h3>Value</h3>
  <pre>{{ formValue() | json }}</pre>
</form>
```

### [Integrating a `FormGroup` into a signal form](#integrating-a-formgroup-into-a-signal-form)

You can also wrap an entire [`FormGroup`](/api/forms/FormGroup). This is common when a reusable sub-section of a form—such as an **Address Block**—is still managed by existing Reactive Forms.

```
import {signal} from '@angular/core';
import {FormGroup, FormControl, Validators} from '@angular/forms';
import {compatForm} from '@angular/forms/signals/compat';

// 1. An existing address group with its own validation logic
const addressGroup = new FormGroup({
  street: new FormControl('123 Angular Way', Validators.required),
  city: new FormControl('Mountain View', Validators.required),
  zip: new FormControl('94043', Validators.required),
});

// 2. Include it in the state like it's a value
const checkoutModel = signal({
  customerName: 'Pirojok the Cat',
  shippingAddress: addressGroup,
});

const f = compatForm(checkoutModel, (p) => {
  required(p.customerName);
});
```

The `shippingAddress` field acts as a branch in your Signal Form tree. You can bind these nested controls in your template by accessing the underlying existing controls via `.control()`:

```
<form novalidate>
  <h3>Shipping Details</h3>

  <div>
    <label>
      Customer Name:
      <input [formField]="f.customerName" />
    </label>

    @if (f.customerName().touched() && f.customerName().invalid()) {
      <div class="error-list">
        <p>Customer name is required.</p>
      </div>
    }
  </div>

  <fieldset>
    <legend>Address</legend>

    @let street = f.shippingAddress().control().controls.street;
    <div>
      <label>
        Street:
        <input [formControl]="street" />
      </label>
      @if (street.touched && street.invalid) {
        <div class="error-list">
          <p>Street is required</p>
        </div>
      }
    </div>

    @let city = f.shippingAddress().control().controls.city;
    <div>
      <label>
        City:
        <input [formControl]="city" />
      </label>
      @if (city.touched && city.invalid) {
        <div class="error-list">
          <p>City is required</p>
        </div>
      }
    </div>

    @let zip = f.shippingAddress().control().controls.zip;
    <div>
      <label>
        Zip Code:
        <input [formControl]="zip" />
      </label>
      @if (zip.touched && zip.invalid) {
        <div class="error-list">
          <p>Zip Code is required</p>
        </div>
      }
    </div>
  </fieldset>
</form>
```

### app.ts

```
import {JsonPipe} from '@angular/common';
import {Component, computed, signal} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
import {FormField, required} from '@angular/forms/signals';
import {compatForm} from '@angular/forms/signals/compat';

@Component({
  selector: 'app',
  imports: [ReactiveFormsModule, FormField, JsonPipe],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  // 1. A legacy address group with its own validation logic
  readonly addressGroup = new FormGroup({
    street: new FormControl('123 Angular Way', Validators.required),
    city: new FormControl('Mountain View', Validators.required),
    zip: new FormControl('94043', Validators.required),
  });

  // 2. Include it in the state like it's a value
  readonly checkoutModel = signal({
    customerName: '',
    shippingAddress: this.addressGroup,
  });

  // 3. Create the form
  readonly f = compatForm(this.checkoutModel, (p) => {
    required(p.customerName);
  });

  // We have to manually extract values, because JSON pipe can't serialize FormControl
  readonly formValue = computed(() => ({
    customerName: this.f.customerName().value(),
    shippingAddress: this.f.shippingAddress().value(),
  }));

  constructor() {
    console.log('Customer Name:', this.f.customerName().value());
    console.log('Street:', this.f.shippingAddress().value().street);
  }
}
```

### app.html

```
<form>
  <h3>Shipping Details</h3>

  <div>
    <label>
      Customer Name:
      <input [formField]="f.customerName" />
    </label>

    @if (f.customerName().touched() && f.customerName().invalid()) {
      <div class="error-list">
        <p>Customer name is required.</p>
      </div>
    }
  </div>

  <fieldset>
    <legend>Address</legend>

    @let street = f.shippingAddress().control().controls.street;
    <div>
      <label>
        Street:
        <input [formControl]="street" />
      </label>
      @if (street.touched && street.invalid) {
        <div class="error-list">
          <p>Street is required</p>
        </div>
      }
    </div>

    @let city = f.shippingAddress().control().controls.city;
    <div>
      <label>
        City:
        <input [formControl]="city" />
      </label>
      @if (city.touched && city.invalid) {
        <div class="error-list">
          <p>City is required</p>
        </div>
      }
    </div>

    @let zip = f.shippingAddress().control().controls.zip;
    <div>
      <label>
        Zip Code:
        <input [formControl]="zip" />
      </label>
      @if (zip.touched && zip.invalid) {
        <div class="error-list">
          <p>Zip Code is required</p>
        </div>
      }
    </div>
  </fieldset>
  <h3>Value</h3>
  <pre>{{ formValue() | json }}</pre>
</form>
```

### [Accessing values](#accessing-values)

While [`compatForm`](/api/forms/signals/compat/compatForm) proxies value access on the [`FormControl`](/api/forms/FormControl) level, the full form value preserves the control:

```
const passwordControl = new FormControl('password' /** ... */);

const user = signal({
  email: '',
  password: passwordControl, // Nest the existing control directly
});

const form = compatForm(user);
form.password().value(); // 'password'
form().value(); // { email: '', password: FormControl}
```

If you need the whole form value, you'd have to build it manually:

```
const formValue = computed(() => ({
  email: form.email().value(),
  password: form.password().value(),
})); // {email: '', password: ''}
```

## [Bottom-up migration](#bottom-up-migration)

### [Integrating a Signal Form into a `FormGroup`](#integrating-a-signal-form-into-a-formgroup)

You can use [`SignalFormControl`](/api/forms/signals/compat/SignalFormControl) to expose a signal-based form as a standard [`FormControl`](/api/forms/FormControl). This is useful when you want to migrate leaf nodes of a form to Signals while keeping the parent [`FormGroup`](/api/forms/FormGroup) structure.

```
import {Component, signal} from '@angular/core';
import {ReactiveFormsModule, FormGroup} from '@angular/forms';
import {SignalFormControl} from '@angular/forms/signals/compat';
import {required} from '@angular/forms/signals';

@Component({
  // ...
  imports: [ReactiveFormsModule],
})
export class UserProfile {
  // 1. Create a SignalFormControl, use signal form rules.
  emailControl = new SignalFormControl('', (p) => {
    required(p, {message: 'Email is required'});
  });

  // 2. Use it in an existing FormGroup
  form = new FormGroup({
    email: this.emailControl,
  });
}
```

The [`SignalFormControl`](/api/forms/signals/compat/SignalFormControl) synchronizes values bi-directionally between the **Signal Forms** system and the **Reactive Forms** system:

* **Signal -> Reactive**: Updating the value via Signal Forms updates the Reactive Form control immediately.

```
// Signal Forms update
this.emailControl.fieldTree().value.set('new@example.com');

// Reactive Forms reflects the change
console.log(this.form.value); // {email: 'new@example.com'}
```

* **Reactive -> Signal**: Updating the value via the parent [`FormGroup`](/api/forms/FormGroup) updates the Signal Forms state.

```
// Reactive Forms update
this.form.patchValue({email: 'other@example.com'});

// Signal Forms reflects the change
console.log(this.emailControl.fieldTree().value()); // 'other@example.com'
```

### [Binding `SignalFormControl`](#binding-signalformcontrol)

To use [`SignalFormControl`](/api/forms/signals/compat/SignalFormControl) in a [`FormGroup`](/api/forms/FormGroup), pass it as a control and bind it in the template using `.fieldTree`:

```
readonly emailControl = new SignalFormControl('', (p) => { required(p); });

readonly form = new FormGroup({
  name: new FormControl('Alice'),
  email: this.emailControl,
});
```

Prefer

```
<form [formGroup]="form">
  <!-- Standard control -->
  <input formControlName="name" />

  <!-- Signal control -->
  <input [formField]="emailControl.fieldTree" />
</form>
```

Avoid

```
<!-- Avoid: Using formControlName or [formControl] for SignalFormControl -->
<input formControlName="email" />
<input [formControl]="emailControl" />
```

### [Why `SignalFormControl` takes a value instead of a signal](#why-signalformcontrol-takes-a-value-instead-of-a-signal)

In standard Signal Forms, you create a form by passing a signal: `form(mySignal)`.

However, [`SignalFormControl`](/api/forms/signals/compat/SignalFormControl) takes a **raw value** (like a string or object) as its first argument:

```
// Takes a raw value, not a signal
const userControl = new SignalFormControl({
  email: 'pirojok@example.com',
});
```

[`SignalFormControl`](/api/forms/signals/compat/SignalFormControl) creates the signal internally to intercept writes and trigger the **synchronous updates** expected by Reactive Forms.

You can still access the internal signal via `.sourceValue`:

```
const value = userControl.sourceValue();
```

### [Disabling/Enabling control](#disabling-enabling-control)

Imperative APIs for changing the enabled/disabled state (like `enable()`, `disable()`) are intentionally not supported in [`SignalFormControl`](/api/forms/signals/compat/SignalFormControl). This is because the state of the control should be derived from the signal state and rules.

Attempting to call disable/enable would throw an error.

Avoid

```
import {signal, effect} from '@angular/core';

export class UserProfile {
  readonly emailControl = new SignalFormControl('');

  readonly isLoading = signal(false);

  constructor() {
    // This will throw an error
    effect(() => {
      if (this.isLoading()) {
        this.emailControl.disable();
      } else {
        this.emailControl.enable();
      }
    });
  }
}
```

Instead, use disabled rule:

Prefer

```
import {signal} from '@angular/core';
import {SignalFormControl} from '@angular/forms/signals/compat';
import {disabled} from '@angular/forms/signals';

export class UserProfile {
  readonly isLoading = signal(false);

  readonly emailControl = new SignalFormControl('', (p) => {
    // The control becomes disabled whenever isLoading is true
    disabled(p, {when: () => this.isLoading()});
  });

  async saveData() {
    this.isLoading.set(true);
    // ... perform save ...
    this.isLoading.set(false);
  }
}
```

### [Dynamic manipulation](#dynamic-manipulation)

Imperative APIs for adding or removing validators (like `addValidators()`, `removeValidators()`, `setValidators()`) are intentionally not supported in [`SignalFormControl`](/api/forms/signals/compat/SignalFormControl).

Attempting to call these methods will throw an error.

Avoid

```
export class UserProfile {
  readonly emailControl = new SignalFormControl('');
  readonly isRequired = signal(false);

  toggleRequired() {
    this.isRequired.update((v) => !v);
    // This will throw an error
    if (this.isRequired()) {
      this.emailControl.addValidators(Validators.required);
    } else {
      this.emailControl.removeValidators(Validators.required);
    }
  }
}
```

Instead, use [`applyWhen`](/api/forms/signals/applyWhen) rule to conditionally apply validators:

Prefer

```
import {signal} from '@angular/core';
import {SignalFormControl} from '@angular/forms/signals/compat';
import {applyWhen, required} from '@angular/forms/signals';

export class UserProfile {
  readonly isRequired = signal(false);

  readonly emailControl = new SignalFormControl('', (p) => {
    // The control becomes required whenever isRequired is true
    applyWhen(
      p,
      () => this.isRequired(),
      (p) => {
        required(p);
      },
    );
  });
}
```

### [Manual Error Selection](#manual-error-selection)

The `setErrors()` and `markAsPending()` methods are not supported. In Signal Forms, errors are derived from validation rules and async validation status. If you need to report an error, it should be done declaratively via a validation rule in the schema.

## [Automatic status classes](#automatic-status-classes)

Reactive/Template Forms automatically adds [class attributes](/guide/forms/template-driven-forms#track-control-states) ( such as `.ng-valid` or `.ng-dirty`) to facilitate styling control states. Signal Forms does not do that.

If you want to preserve this behavior, you can provide the [`NG_STATUS_CLASSES`](/api/forms/signals/compat/NG_STATUS_CLASSES) preset:

```
import {provideSignalFormsConfig} from '@angular/forms/signals';
import {NG_STATUS_CLASSES} from '@angular/forms/signals/compat';

bootstrapApplication(App, {
  providers: [
    provideSignalFormsConfig({
      classes: NG_STATUS_CLASSES,
    }),
  ],
});
```

You can also provide your own custom configuration to apply whatever classes you wish based on you custom logic:

```
import {provideSignalFormsConfig} from '@angular/forms/signals';

bootstrapApplication(App, {
  providers: [
    provideSignalFormsConfig({
      classes: {
        'ng-valid': ({state}) => state().valid(),
        'ng-invalid': ({state}) => state().invalid(),
        'ng-touched': ({state}) => state().touched(),
        'ng-dirty': ({state}) => state().dirty(),
      },
    }),
  ],
});
```

## [Custom Controls](#custom-controls)

Any [custom Signal Form Control](guide/forms/signals/custom-controls) can be used with Reactive (and Template-Driven) Forms as-is. This allows you to migrate existing [`ControlValueAccessor`](/api/forms/ControlValueAccessor) implementations to [`FormValueControl`](/api/forms/signals/FormValueControl)/[`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl) without breaking existing usages.

**IMPORTANT:** Do **not** implement both [`ControlValueAccessor`](/api/forms/ControlValueAccessor) and [`FormValueControl`](/api/forms/signals/FormValueControl)/[`FormCheckboxControl`](/api/forms/signals/FormCheckboxControl) on the same component. Implement one or the other.

Given the following custom control:

```
import {Component, model} from '@angular/core';
import {FormValueControl} from '@angular/forms/signals';

@Component({
  selector: 'app-basic-input',
  template: `
    <div class="basic-input">
      <input
        type="text"
        [value]="value()"
        (input)="value.set($event.target.value)"
        placeholder="Enter text..."
      />
    </div>
  `,
})
export class BasicInput implements FormValueControl<string> {
  /** The current input value */
  value = model('');
}
```

You can use this custom control with reactive forms as you would a native input or a custom control based on [`ControlValueAccessor`](/api/forms/ControlValueAccessor). For example, consider this simple component with a Reactive Form.

```
import {Component} from '@angular/core';
import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';
import {BasicInput} from './basic-input';

@Component({
  selector: 'app-example',
  template: `
    <form [formGroup]="reactiveFormGroup">
      <app-basic-input formControlName="reactiveControlName" />
    </form>
    <p>Text: {{ reactiveFormGroup.value.reactiveControlName }}</p>
  `,
  imports: [ReactiveFormsModule, BasicInput],
})
export class ExampleComponent {
  readonly reactiveFormGroup = new FormGroup({
    reactiveControlName: new FormControl(''),
  });
}
```

Any change to the custom `app-basic-input` control will be reflected in the reactive [`FormControl`](/api/forms/FormControl).

----
url: https://angular.dev/guide/di/creating-and-using-services
----

Services are reusable pieces of code that you can share across your Angular application. You commonly use them to handle data fetching, business logic, or other functionality that multiple components need to access.

## [Creating a service](#creating-a-service)

You can create a service using the [Angular CLI](tools/cli) with the following command:

```
ng generate service CUSTOM_NAME
```

This command creates a dedicated `CUSTOM_NAME.ts` file in your `src` directory.

You can also manually create a service by adding the [`@Service()`](/api/core/Service) decorator to a TypeScript class. This tells Angular that you can use the class as an injectable dependency.

The following example defines a service that allows users to add and retrieve data:

### src/app/basic-data-store.ts

```
import {Service} from '@angular/core';

@Service()
export class BasicDataStore {
  private data: string[] = [];

  addData(item: string): void {
    this.data.push(item);
  }

  getData(): string[] {
    return [...this.data];
  }
}
```

## [How services become available](#how-services-become-available)

Services are provisioned at the root level by default. When a service is provided globally, Angular guarantees three main benefits:

* **Singleton Instance:** Creates a single, shared instance for the entire application.
* **Global Availability:** Automatically accessible anywhere without manual provider registration.
* **Tree-shakability:** Ensures the service is excluded from the final production bundle if your code never explicitly uses it.

### [Using the `@Service` vs `@Injectable` decorator](#using-the-service-vs-injectable-decorator)

The [`@Service`](/api/core/Service) decorator serves as a modern, ergonomic shorthand for the traditional `@Injectable({ providedIn: 'root' })` syntax.

Use this quick reference to decide which decorator fits your scenario:

| Feature / Requirement                         | [`@Service`](/api/core/Service) | [`@Injectable`](/api/core/Injectable)  |
| --------------------------------------------- | ------------------------------- | -------------------------------------- |
| **`inject()` function support**               | Yes                             | Yes                                    |
| **Constructor-based DI**                      | ❌ No                            | Yes                                    |
| **Implicit root singleton provider**          | Yes                             | ❌ No (requires `{providedIn: 'root'}`) |
| **Advanced provider keys (`useClass`, etc.)** | ❌ No                            | Yes                                    |
| **Custom initialization factories**           | Yes                             | Yes                                    |
| **Non-root scopes (`platform`, etc.)**        | ❌ No                            | Yes                                    |

### [Replacing the implementation with a factory](#replacing-the-implementation-with-a-factory)

If you need to control how the singleton is created, for example, to swap in a different implementation depending on the environment, pass a `factory` function.

The factory runs in an [injection context](guide/di/dependency-injection-context), so you can use [`inject()`](api/core/inject) inside it to read other dependencies.

The following `Analytics` service is a no-op locally so events don't pollute the console during development. In production, the factory reads an `ANALYTICS_ENABLED` token and returns a `GoogleAnalytics` subclass that forwards events to the real tracker:

### src/app/analytics.ts

```
import {inject, InjectionToken, Service} from '@angular/core';
import {ANALYTICS_ENABLED} from './token';

@Service({
  factory: () => (inject(ANALYTICS_ENABLED) ? new GoogleAnalytics() : new Analytics()),
})
export class Analytics {
  track(event: string, payload?: Record<string, unknown>) {
    // No-op by default.
  }
}

class GoogleAnalytics extends Analytics {
  override track(event: string, payload?: Record<string, unknown>) {
    // Dispatches an analytics event to Google Analytics
  }
}
```

**NOTE:** The `factory` option replaces the `useClass`, `useValue`, `useExisting`, and `useFactory` options of [`@Injectable`](/api/core/Injectable). If you need any of those, keep using [`@Injectable`](/api/core/Injectable).

### [Opting out of automatic provisioning](#opting-out-of-automatic-provisioning)

By default, [`@Service`](/api/core/Service) provides the class at the root injector. If you want to provide it manually, for example, to scope it to a specific route or component, set `autoProvided: false`:

### src/app/analytics-logger.ts

```
import {Service} from '@angular/core';

@Service({autoProvided: false})
export class AnalyticsLogger {
  trackEvent(name: string) {
    console.log('event:', name);
  }
}
```

You are then responsible for adding the service to a `providers` array, just like with a plain [`@Injectable()`](/api/core/Injectable):

### [When to use `@Service` vs `@Injectable`](#when-to-use-service-vs-injectable)

Reach for [`@Service`](/api/core/Service) when you are creating a new singleton class that uses `inject()` for its dependencies. Keep using [`@Injectable`](/api/core/Injectable) when you need any of the following:

* **Constructor-based dependency injection.** [`@Service`](/api/core/Service) only supports the [`inject()`](api/core/inject) function.
* **Advanced provider configuration** such as `useClass`, `useValue`, `useExisting`, or `useFactory`. [`@Service`](/api/core/Service) exposes a single `factory` option instead.
* **Non-root scopes** such as `providedIn: 'platform'`.

## [Injecting a service](#injecting-a-service)

Once you've created a service with `providedIn: 'root'`, you can inject it anywhere in your application using the `inject()` function from `@angular/core`.

### [Injecting into a component](#injecting-into-a-component)

```
import {Component, inject} from '@angular/core';
import {BasicDataStore} from './basic-data-store';

@Component({
  selector: 'app-example',
  template: `
    <div>
      <p>{{ dataStore.getData() }}</p>
      <button (click)="dataStore.addData('More data')">Add more data</button>
    </div>
  `,
})
export class Example {
  dataStore = inject(BasicDataStore);
}
```

### [Injecting into another service](#injecting-into-another-service)

```
import {inject, Service} from '@angular/core';
import {AdvancedDataStore} from './advanced-data-store';

@Service()
export class BasicDataStore {
  private advancedDataStore = inject(AdvancedDataStore);
  private data: string[] = [];

  addData(item: string): void {
    this.data.push(item);
  }

  getData(): string[] {
    return [...this.data, ...this.advancedDataStore.getData()];
  }
}
```

## [Next steps](#next-steps)

While `providedIn: 'root'` covers most use cases, Angular also provides additional ways you can configure services for more specialized scenarios:

* **Component-specific instances** - When components need their own isolated service instances
* **Manual configuration** - For services that require runtime configuration
* **Factory providers** - For dynamic service creation based on runtime conditions
* **Value providers** - For providing configuration objects or constants

You can learn more about these advanced patterns in the next guide: [defining dependency providers](/guide/di/defining-dependency-providers).

----
url: https://angular.dev/api/router/RouterStateSnapshot
----

```
    
      class RouterStateSnapshot extends Tree<ActivatedRouteSnapshot> {}
    
    
```

@paramurl`string`

The url from which this snapshot was created

@paramroot`TreeNode<ActivatedRouteSnapshot>`

@returns`RouterStateSnapshot`

The url from which this snapshot was created

@returns`string`

## [Description](#description)

Represents the state of the router at a moment in time.

This is a tree of activated route snapshots. Every node in this tree knows about the "consumed" URL segments, the extracted parameters, and the resolved data.

The following example shows how a component is initialized with information from the snapshot of the root node's state at the time of creation.

```
@Component({templateUrl:'template.html'})
class MyComponent {
  constructor(router: Router) {
    const state: RouterState = router.routerState;
    const snapshot: RouterStateSnapshot = state.snapshot;
    const root: ActivatedRouteSnapshot = snapshot.root;
    const child = root.firstChild;
    const id: Observable<string> = child.params.map(p => p.id);
    //...
  }
}
```

Jump to details

----
url: https://angular.dev/ecosystem/service-workers/config
----

This topic describes the properties of the service worker configuration file.

## [Modifying the configuration](/ecosystem/service-workers/config#modifying-the-configuration)

The `ngsw-config.json` JSON configuration file specifies which files and data URLs the Angular service worker should cache and how it should update the cached files and data. The [Angular CLI](tools/cli) processes this configuration file during `ng build`.

All file paths must begin with `/`, which corresponds to the deployment directory — usually `dist/<project-name>` in CLI projects.

Unless otherwise commented, patterns use a **limited\*** glob format that internally will be converted into regex:

| Glob formats | Details                                                                                                |
| :----------- | :----------------------------------------------------------------------------------------------------- |
| `**`         | Matches 0 or more path segments                                                                        |
| `*`          | Matches 0 or more characters excluding `/`                                                             |
| `?`          | Matches exactly one character excluding `/`                                                            |
| `!` prefix   | Marks the pattern as being negative, meaning that only files that don't match the pattern are included |

### Special characters need to be escaped

Pay attention that some characters with a special meaning in a regular expression are not escaped and also the pattern is not wrapped in `^`/`$` in the internal glob to regex conversion.

`$` is a special character in regex that matches the end of the string and will not be automatically escaped when converting the glob pattern to a regular expression.

If you want to literally match the `$` character, you have to escape it yourself (with `\\$`). For example, the glob pattern `/foo/bar/$value` results in an unmatchable expression, because it is impossible to have a string that has any characters after it has ended.

The pattern will not be automatically wrapped in `^` and `$` when converting it to a regular expression. Therefore, the patterns will partially match the request URLs.

If you want your patterns to match the beginning and/or end of URLs, you can add `^`/`$` yourself. For example, the glob pattern `/foo/bar/*.js` will match both `.js` and `.json` files. If you want to only match `.js` files, use `/foo/bar/*.js$`.

Example patterns:

| Patterns     | Details                               |
| :----------- | :------------------------------------ |
| `/**/*.html` | Specifies all HTML files              |
| `/*.html`    | Specifies only HTML files in the root |
| `!/**/*.map` | Exclude all sourcemaps                |

## [Service worker configuration properties](/ecosystem/service-workers/config#service-worker-configuration-properties)

The following sections describe each property of the configuration file.

### [`appData`](/ecosystem/service-workers/config#appdata)

This section enables you to pass any data you want that describes this particular version of the application. The [`SwUpdate`](/api/service-worker/SwUpdate) service includes that data in the update notifications. Many applications use this section to provide additional information for the display of UI popups, notifying users of the available update.

### [`index`](/ecosystem/service-workers/config#index)

Specifies the file that serves as the index page to satisfy navigation requests. Usually this is `/index.html`.

### [`assetGroups`](/ecosystem/service-workers/config#assetgroups)

*Assets* are resources that are part of the application version that update along with the application. They can include resources loaded from the page's origin as well as third-party resources loaded from CDNs and other external URLs. As not all such external URLs might be known at build time, URL patterns can be matched.

**HELPFUL:** For the service worker to handle resources that are loaded from different origins, make sure that [CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) is correctly configured on each origin's server.

This field contains an array of asset groups, each of which defines a set of asset resources and the policy by which they are cached.

```
{
  "assetGroups": [
    {
      …
    },
    {
      …
    }
  ]
}
```

**HELPFUL:** When the ServiceWorker handles a request, it checks asset groups in the order in which they appear in `ngsw-config.json`. The first asset group that matches the requested resource handles the request.

It is recommended that you put the more specific asset groups higher in the list. For example, an asset group that matches `/foo.js` should appear before one that matches `*.js`.

Each asset group specifies both a group of resources and a policy that governs them. This policy determines when the resources are fetched and what happens when changes are detected.

Asset groups follow the TypeScript interface shown here:

```
interface AssetGroup {
  name: string;
  installMode?: 'prefetch' | 'lazy';
  updateMode?: 'prefetch' | 'lazy';
  resources: {
    files?: string[];
    urls?: string[];
  };
  cacheQueryOptions?: {
    ignoreSearch?: boolean;
  };
}
```

Each `AssetGroup` is defined by the following asset group properties.

#### [`name`](/ecosystem/service-workers/config#name)

A `name` is mandatory. It identifies this particular group of assets between versions of the configuration.

#### [`installMode`](/ecosystem/service-workers/config#installmode)

The `installMode` determines how these resources are initially cached. The `installMode` can be either of two values:

| Values     | Details                                                                                                                                                                                                                                                                                                                                                                                       |
| :--------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prefetch` | Tells the Angular service worker to fetch every single listed resource while it's caching the current version of the application. This is bandwidth-intensive but ensures resources are available whenever they're requested, even if the browser is currently offline.                                                                                                                       |
| `lazy`     | Does not cache any of the resources up front. Instead, the Angular service worker only caches resources for which it receives requests. This is an on-demand caching mode. Resources that are never requested are not cached. This is useful for things like images at different resolutions, so the service worker only caches the correct assets for the particular screen and orientation. |

Defaults to `prefetch`.

#### [`updateMode`](/ecosystem/service-workers/config#updatemode)

For resources already in the cache, the `updateMode` determines the caching behavior when a new version of the application is discovered. Any resources in the group that have changed since the previous version are updated in accordance with `updateMode`.

| Values     | Details                                                                                                                                                                                                                                  |
| :--------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prefetch` | Tells the service worker to download and cache the changed resources immediately.                                                                                                                                                        |
| `lazy`     | Tells the service worker to not cache those resources. Instead, it treats them as unrequested and waits until they're requested again before updating them. An `updateMode` of `lazy` is only valid if the `installMode` is also `lazy`. |

Defaults to the value `installMode` is set to.

#### [`resources`](/ecosystem/service-workers/config#resources)

This section describes the resources to cache, broken up into the following groups:

| Resource groups | Details                                                                                                                                                                                                                                                                                                                                                                                                |
| :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `files`         | Lists patterns that match files in the distribution directory. These can be single files or glob-like patterns that match a number of files.                                                                                                                                                                                                                                                           |
| `urls`          | Includes both URLs and URL patterns that are matched at runtime. These resources are not fetched directly and do not have content hashes, but they are cached according to their HTTP headers. This is most useful for CDNs such as the Google Fonts service. *(Negative glob patterns are not supported and `?` will be matched literally; that is, it will not match any character other than `?`.)* |

#### [`cacheQueryOptions`](/ecosystem/service-workers/config#cachequeryoptions)

These options are used to modify the matching behavior of requests. They are passed to the browsers `Cache#match` function. See [MDN](https://developer.mozilla.org/docs/Web/API/Cache/match) for details. Currently, only the following options are supported:

| Options        | Details                                       |
| :------------- | :-------------------------------------------- |
| `ignoreSearch` | Ignore query parameters. Defaults to `false`. |

### [`dataGroups`](/ecosystem/service-workers/config#datagroups)

Unlike asset resources, data requests are not versioned along with the application. They're cached according to manually-configured policies that are more useful for situations such as API requests and other data dependencies.

This field contains an array of data groups, each of which defines a set of data resources and the policy by which they are cached.

```
{
  "dataGroups": [
    {
      …
    },
    {
      …
    }
  ]
}
```

**HELPFUL:** When the ServiceWorker handles a request, it checks data groups in the order in which they appear in `ngsw-config.json`. The first data group that matches the requested resource handles the request.

It is recommended that you put the more specific data groups higher in the list. For example, a data group that matches `/api/foo.json` should appear before one that matches `/api/*.json`.

Data groups follow this TypeScript interface:

```
export interface DataGroup {
  name: string;
  urls: string[];
  version?: number;
  cacheConfig: {
    maxSize: number;
    maxAge: string;
    timeout?: string;
    refreshAhead?: string;
    strategy?: 'freshness' | 'performance';
  };
  cacheQueryOptions?: {
    ignoreSearch?: boolean;
  };
}
```

Each `DataGroup` is defined by the following data group properties.

#### [`name`](/ecosystem/service-workers/config#datagroups-name)

Similar to `assetGroups`, every data group has a `name` which uniquely identifies it.

#### [`urls`](/ecosystem/service-workers/config#urls)

A list of URL patterns. URLs that match these patterns are cached according to this data group's policy. Only non-mutating requests (GET and HEAD) are cached.

* Negative glob patterns are not supported
* `?` is matched literally; that is, it matches *only* the character `?`

#### [`version`](/ecosystem/service-workers/config#version)

Occasionally APIs change formats in a way that is not backward-compatible. A new version of the application might not be compatible with the old API format and thus might not be compatible with existing cached resources from that API.

`version` provides a mechanism to indicate that the resources being cached have been updated in a backwards-incompatible way, and that the old cache entries —those from previous versions— should be discarded.

`version` is an integer field and defaults to `1`.

#### [`cacheConfig`](/ecosystem/service-workers/config#cacheconfig)

The following properties define the policy by which matching requests are cached.

##### [`maxSize`](/ecosystem/service-workers/config#maxsize)

The maximum number of entries, or responses, in the cache.

**CRITICAL:** Open-ended caches can grow in unbounded ways and eventually exceed storage quotas, resulting in eviction.

##### [`maxAge`](/ecosystem/service-workers/config#maxage)

The `maxAge` parameter indicates how long responses are allowed to remain in the cache before being considered invalid and evicted. `maxAge` is a duration string, using the following unit suffixes:

| Suffixes | Details      |
| :------- | :----------- |
| `d`      | Days         |
| `h`      | Hours        |
| `m`      | Minutes      |
| `s`      | Seconds      |
| `u`      | Milliseconds |

For example, the string `3d12h` caches content for up to three and a half days.

##### [`timeout`](/ecosystem/service-workers/config#timeout)

This duration string specifies the network timeout. The network timeout is how long the Angular service worker waits for the network to respond before using a cached response, if configured to do so. `timeout` is a duration string, using the following unit suffixes:

| Suffixes | Details      |
| :------- | :----------- |
| `d`      | Days         |
| `h`      | Hours        |
| `m`      | Minutes      |
| `s`      | Seconds      |
| `u`      | Milliseconds |

For example, the string `5s30u` translates to five seconds and 30 milliseconds of network timeout.

##### [`refreshAhead`](/ecosystem/service-workers/config#refreshahead)

This duration string specifies the time ahead of the expiration of a cached resource when the Angular service worker should proactively attempt to refresh the resource from the network. The `refreshAhead` duration is an optional configuration that determines how much time before the expiration of a cached response the service worker should initiate a request to refresh the resource from the network.

| Suffixes | Details      |
| :------- | :----------- |
| `d`      | Days         |
| `h`      | Hours        |
| `m`      | Minutes      |
| `s`      | Seconds      |
| `u`      | Milliseconds |

For example, the string `1h30m` translates to one hour and 30 minutes ahead of the expiration time.

##### [`strategy`](/ecosystem/service-workers/config#strategy)

The Angular service worker can use either of two caching strategies for data resources.

| Caching strategies | Details                                                                                                                                                                                                                                                                                                                                                   |
| :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `performance`      | The default, optimizes for responses that are as fast as possible. If a resource exists in the cache, the cached version is used, and no network request is made. This allows for some staleness, depending on the `maxAge`, in exchange for better performance. This is suitable for resources that don't change often; for example, user avatar images. |
| `freshness`        | Optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to `timeout`, does the request fall back to the cache. This is useful for resources that change frequently; for example, account balances.                                                                              |

**HELPFUL:** You can also emulate a third strategy, [staleWhileRevalidate](https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate), which returns cached data if it is available, but also fetches fresh data from the network in the background for next time. To use this strategy set `strategy` to `freshness` and `timeout` to `0u` in `cacheConfig`.

This essentially does the following:

1. Try to fetch from the network first.
2. If the network request does not complete immediately, that is after a timeout of 0 ms, ignore the cache age and fall back to the cached value.
3. Once the network request completes, update the cache for future requests.
4. If the resource does not exist in the cache, wait for the network request anyway.

##### [`cacheOpaqueResponses`](/ecosystem/service-workers/config#cacheopaqueresponses)

Whether the Angular service worker should cache opaque responses or not.

If not specified, the default value depends on the data group's configured strategy:

| Strategies                             | Details                                                                                                                                                                                                                                                                                                                     |
| :------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Groups with the `freshness` strategy   | The default value is `true` and the service worker caches opaque responses. These groups will request the data every time and only fall back to the cached response when offline or on a slow network. Therefore, it doesn't matter if the service worker caches an error response.                                         |
| Groups with the `performance` strategy | The default value is `false` and the service worker doesn't cache opaque responses. These groups would continue to return a cached response until `maxAge` expires, even if the error was due to a temporary network or server issue. Therefore, it would be problematic for the service worker to cache an error response. |

### Comment on opaque responses

In case you are not familiar, an [opaque response](https://fetch.spec.whatwg.org#concept-filtered-response-opaque) is a special type of response returned when requesting a resource that is on a different origin which doesn't return CORS headers. One of the characteristics of an opaque response is that the service worker is not allowed to read its status, meaning it can't check if the request was successful or not. See [Introduction to `fetch()`](https://developers.google.com/web/updates/2015/03/introduction-to-fetch#response_types) for more details.

If you are not able to implement CORS — for example, if you don't control the origin — prefer using the `freshness` strategy for resources that result in opaque responses.

#### [`cacheQueryOptions`](/ecosystem/service-workers/config#datagroups-cachequeryoptions)

See [assetGroups](/ecosystem/service-workers/config#assetgroups) for details.

### [`navigationUrls`](/ecosystem/service-workers/config#navigationurls)

This optional section enables you to specify a custom list of URLs that will be redirected to the index file.

#### [Handling navigation requests](/ecosystem/service-workers/config#handling-navigation-requests)

The ServiceWorker redirects navigation requests that don't match any `asset` or `data` group to the specified [index file](/ecosystem/service-workers/config#index). A request is considered to be a navigation request if:

* Its [method](https://developer.mozilla.org/docs/Web/API/Request/method) is `GET`

* Its [mode](https://developer.mozilla.org/docs/Web/API/Request/mode) is `navigate`

* It accepts a `text/html` response as determined by the value of the `Accept` header

* Its URL matches the following criteria:

  * The URL must not contain a file extension (that is, a `.`) in the last path segment
  * The URL must not contain `__`

**HELPFUL:** To configure whether navigation requests are sent through to the network or not, see the [navigationRequestStrategy](/ecosystem/service-workers/config#navigationrequeststrategy) section and [applicationMaxAge](/ecosystem/service-workers/config#applicationmaxage) sections.

#### [Matching navigation request URLs](/ecosystem/service-workers/config#matching-navigation-request-urls)

While these default criteria are fine in most cases, it is sometimes desirable to configure different rules. For example, you might want to ignore specific routes, such as those that are not part of the Angular app, and pass them through to the server.

This field contains an array of URLs and [glob-like](/ecosystem/service-workers/config#modifying-the-configuration) URL patterns that are matched at runtime. It can contain both negative patterns (that is, patterns starting with `!`) and non-negative patterns and URLs.

Only requests whose URLs match *any* of the non-negative URLs/patterns and *none* of the negative ones are considered navigation requests. The URL query is ignored when matching.

If the field is omitted, it defaults to:

```
[
  '/**', // Include all URLs.
  '!/**/*.*', // Exclude URLs to files (containing a file extension in the last segment).
  '!/**/*__*', // Exclude URLs containing `__` in the last segment.
  '!/**/*__*/**', // Exclude URLs containing `__` in any other segment.
];
```

### [`navigationRequestStrategy`](/ecosystem/service-workers/config#navigationrequeststrategy)

This optional property enables you to configure how the service worker handles navigation requests:

```
{
  "navigationRequestStrategy": "freshness"
}
```

| Possible values | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'performance'` | The default setting. Serves the specified [index file](/ecosystem/service-workers/config#index), which is typically cached.                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `'freshness'`   | Passes the requests through to the network and falls back to the `performance` behavior when offline. This value is useful when the server redirects the navigation requests elsewhere using a `3xx` HTTP redirect status code. Reasons for using this value include:- Redirecting to an authentication website when authentication is not handled by the application
- Redirecting specific URLs to avoid breaking existing links/bookmarks after a website redesign
- Redirecting to a different website, such as a server-status page, while a page is temporarily down |

**IMPORTANT:** The `freshness` strategy usually results in more requests sent to the server, which can increase response latency. It is recommended that you use the default performance strategy whenever possible.

### [`applicationMaxAge`](/ecosystem/service-workers/config#applicationmaxage)

This optional property enables you to configure how long the service worker will cache any requests. Within the `maxAge`, files will be served from cache. Beyond it, all requests will only be served from the network, including asset and data requests.

----
url: https://angular.dev/reference/migrations/ngstyle-to-style
----

This schematic migrates NgStyle directive usages to style bindings in your application. It will only migrate usages that are considered safe to migrate.

Run the schematic using the following command:

```
ng generate @angular/core:ngstyle-to-style
```

An object with a single key becomes a binding to that one style property.

#### [Before](#before)

```
<div [ngStyle]="{'background-color': 'red'}"></div>
```

#### [After](#after)

```
<div [style.background-color]="'red'"></div>
```

An object with more than one key becomes a single `[style]` binding.

#### [Before](#multiple-keys-before)

```
<div [ngStyle]="{'color': 'blue', 'font-weight': 'bold'}"></div>
```

#### [After](#multiple-keys-after)

```
<div [style]="{'color': 'blue', 'font-weight': 'bold'}"></div>
```

## [Configuration options](#configuration-options)

The migration supports a few options for fine tuning the migration to your specific needs.

### [`--best-effort-mode`](#--best-effort-mode)

By default, the migration avoids migrating object references usages of [`NgStyle`](/api/common/NgStyle) When the `--best-effort-mode` flag is enabled, [`ngStyle`](/api/common/NgStyle) instances binded to object references are also migrated. This can be unsafe to migrate, for example if the binded object is mutated.

```
<div [ngStyle]="styleObject"></div>
```

to

```
<div [style]="styleObject"></div>
```

----
url: https://angular.dev/api/common/getLocaleExtraDayPeriodRules
----

Angular recommends relying on the `Intl` API for i18n. Let `Intl.DateTimeFormat` determine the day period instead.

## [API](#api)

```
function getLocaleExtraDayPeriodRules(locale: string): (Time | [Time, Time])[];
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Let `Intl.DateTimeFormat` determine the day period instead.

@paramlocale`string`

A locale code for the locale format rules to use.

@returns`(Time | [Time, Time])[]`

The rules for the locale, a single time value or array of *from-time, to-time*, or null if no periods are available.

## [Description](#description)

Retrieves locale-specific rules used to determine which day period to use when more than one period is defined for a locale.

There is a rule for each defined day period. The first rule is applied to the first day period and so on. Fall back to AM/PM when no rules are available.

A rule can specify a period as time range, or as a single time value.

This functionality is only available when you have loaded the full locale data. See the ["I18n guide"](guide/i18n/format-data-locale).

Jump to details

----
url: https://angular.dev/cli/generate/config
----

```
ng generate config [type] [options]
```

Generates configuration files for your project. These files control various aspects of your project's build process, testing, and browser compatibility. This schematic helps you create or update essential configuration files with ease.

## [Arguments](#arguments)

`type`

Specifies the type of configuration file to generate.

Value Type`string`Allowed Values`browserslist`, `karma`, `vitest`

## [Options](#options)

`project`

The name of the project where the configuration file should be created or updated.

Value Type`string`

Jump to details

----
url: https://angular.dev/api/forms/signals/MIN_DATE
----

```
    
      const MIN_DATE: LimitKey<Date>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/isFormRecord
----

```
    
      const isFormRecord: (control: unknown) => control is FormRecord<AbstractControl<any, any, any>>;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/convertToParamMap
----

```
function convertToParamMap(params: Params): ParamMap;
```

@paramparams`Params`

The instance to convert.

@returns`ParamMap`

The new map instance.

Jump to details

----
url: https://angular.dev/errors/NG05000
----

This warning means that the hydration was enabled for an application that was configured to use an unsupported version of Zone.js: either a custom or a "noop" one (see more info [here](api/core/BootstrapOptions#ngZone)).

Hydration relies on a signal from Zone.js when it becomes stable inside an application, so that Angular can start the serialization process on the server or post-hydration cleanup on the client (to remove DOM nodes that remained unclaimed).

Providing a custom or a "noop" Zone.js implementation may lead to a different timing of the "stable" event, thus triggering the serialization or the cleanup too early or too late. This is not yet a fully supported configuration.

If you use a custom Zone.js implementation, make sure that the "onStable" event is emitted at the right time and does not result in incorrect application behavior with hydration.

More information about hydration can be found in the [hydration guide](guide/hydration).

----
url: https://angular.dev/api/common/getLocaleNumberFormat
----

Angular recommends relying on the `Intl` API for i18n. Let `Intl.NumberFormat` determine the number format instead

## [API](#api)

```
function getLocaleNumberFormat(locale: string, type: NumberFormatStyle): string;
```

@deprecated

Angular recommends relying on the `Intl` API for i18n. Let `Intl.NumberFormat` determine the number format instead

@paramlocale`string`

A locale code for the locale format rules to use.

@paramtype`NumberFormatStyle`

The type of numeric value to be formatted (such as `Decimal` or `Currency`.)

@returns`string`

The localized format string.

## [Description](#description)

Retrieves a number format for a given locale.

Numbers are formatted using patterns, like `#,###.00`. For example, the pattern `#,###.00` when used to format the number 12345.678 could result in "12'345,678". That would happen if the grouping separator for your language is an apostrophe, and the decimal separator is a comma.

**Important:** The characters `.` `,` `0` `#` (and others below) are special placeholders that stand for the decimal separator, and so on, and are NOT real characters. You must NOT "translate" the placeholders. For example, don't change `.` to `,` even though in your language the decimal point is written with a comma. The symbols should be replaced by the local equivalents, using the appropriate [`NumberSymbol`](/api/common/NumberSymbol) for your language.

Here are the special characters used in number patterns:

| Symbol | Meaning                                                                            |
| ------ | ---------------------------------------------------------------------------------- |
| .      | Replaced automatically by the character used for the decimal point.                |
| ,      | Replaced by the "grouping" (thousands) separator.                                  |
| 0      | Replaced by a digit (or zero if there aren't enough digits).                       |
| #      | Replaced by a digit (or nothing if there aren't enough).                           |
| ¤      | Replaced by a currency symbol, such as $ or USD.                                   |
| %      | Marks a percent format. The % symbol may change position, but must be retained.    |
| E      | Marks a scientific format. The E symbol may change position, but must be retained. |
| '      | Special characters used as literal characters are quoted with ASCII single quotes. |

Jump to details

----
url: https://angular.dev/cli/generate
----

```
ng generate <schematic> [options]
```

```
ng g <schematic> [options]
```

Generates and/or modifies files based on a schematic.

### Sub-commands

This command has the following sub-commands

* [ai-config](cli/generate/ai-config)
* [app-shell](cli/generate/app-shell)
* [application](cli/generate/application)
* [class](cli/generate/class)
* [component](cli/generate/component)
* [config](cli/generate/config)
* [directive](cli/generate/directive)
* [enum](cli/generate/enum)
* [environments](cli/generate/environments)
* [guard](cli/generate/guard)
* [interceptor](cli/generate/interceptor)
* [interface](cli/generate/interface)
* [library](cli/generate/library)
* [module](cli/generate/module)
* [pipe](cli/generate/pipe)
* [resolver](cli/generate/resolver)
* [service](cli/generate/service)
* [service-worker](cli/generate/service-worker)
* [web-worker](cli/generate/web-worker)

## [Arguments](#arguments)

`schematic`

The \[collection:schematic] to run.

Jump to details

----
url: https://angular.dev/api/core/APP_INITIALIZER
----

from v19.0.0, use provideAppInitializer instead

## [API](#api)

```
    
      const APP_INITIALIZER: InjectionToken<readonly (() => any)[]>;
    
    
```

## [Description](#description)

A DI token that you can use to provide one or more initialization functions.

The provided functions are injected at application startup and executed during app initialization. If any of these functions returns a Promise or an Observable, initialization does not complete until the Promise is resolved or the Observable is completed.

You can, for example, create a factory function that loads language data or an external configuration, and provide that function to the [`APP_INITIALIZER`](/api/core/APP_INITIALIZER) token. The function is executed during the application bootstrap process, and the needed data is available on startup.

Note that the provided initializer is run in the injection context.

## [Usage Notes](#usage-notes)

The following example illustrates how to configure a multi-provider using [`APP_INITIALIZER`](/api/core/APP_INITIALIZER) token and a function returning a promise.

### [Example with NgModule-based application](#example-with-ngmodule-based-application)

```
function initializeApp(): Promise<any> {
  const http = inject(HttpClient);
  return firstValueFrom(
    http
      .get("https://someUrl.com/api/user")
      .pipe(tap(user => { ... }))
  );
}

@NgModule({
 imports: [BrowserModule],
 declarations: [AppComponent],
 bootstrap: [AppComponent],
 providers: [{
   provide: APP_INITIALIZER,
   useValue: initializeApp,
   multi: true,
  }]
 })
export class AppModule {}
```

### [Example with standalone application](#example-with-standalone-application)

```
function initializeApp() {
  const http = inject(HttpClient);
  return firstValueFrom(
    http
      .get("https://someUrl.com/api/user")
      .pipe(tap(user => { ... }))
  );
}

bootstrapApplication(App, {
  providers: [
    provideHttpClient(),
    {
      provide: APP_INITIALIZER,
      useValue: initializeApp,
      multi: true,
    },
  ],
});
```

It's also possible to configure a multi-provider using [`APP_INITIALIZER`](/api/core/APP_INITIALIZER) token and a function returning an observable, see an example below. Note: the `HttpClient` in this example is used for demo purposes to illustrate how the factory function can work with other providers available through DI.

### [Example with NgModule-based application](#example-with-ngmodule-based-application)

```
function initializeApp() {
  const http = inject(HttpClient);
  return firstValueFrom(
    http
      .get("https://someUrl.com/api/user")
      .pipe(tap(user => { ... }))
  );
}

@NgModule({
  imports: [BrowserModule, HttpClientModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  providers: [{
    provide: APP_INITIALIZER,
    useValue: initializeApp,
    multi: true,
  }]
})
export class AppModule {}
```

### [Example with standalone application](#example-with-standalone-application)

```
function initializeApp() {
  const http = inject(HttpClient);
  return firstValueFrom(
    http
      .get("https://someUrl.com/api/user")
      .pipe(tap(user => { ... }))
  );
}

bootstrapApplication(App, {
  providers: [
    provideHttpClient(),
    {
      provide: APP_INITIALIZER,
      useValue: initializeApp,
      multi: true,
    },
  ],
});
```

Jump to details

----
url: https://angular.dev/api/forms/signals/compat/CompatFormOptions
----

```
    
      type CompatFormOptions<TModel> = Omit<FormOptions<TModel>, 'adapter'>
    
    
```

Jump to details

----
url: https://angular.dev/api/ssr/PrerenderFallback
----

```
    
      enum PrerenderFallback {}
    
    
```

Fallback to Server-Side Rendering (SSR) if the pre-rendered path is not available. This strategy dynamically generates the page on the server at request time.

Fallback to Client-Side Rendering (CSR) if the pre-rendered path is not available. This strategy allows the page to be rendered on the client side.

No fallback; if the path is not pre-rendered, the server will not handle the request. This means the application will not provide any response for paths that are not pre-rendered.

Jump to details

----
url: https://angular.dev/api/core/enableProdMode
----

```
function enableProdMode(): void;
```

@returns`void`

## [Description](#description)

Disable Angular's development mode, which turns off assertions and other checks within the framework.

One important assertion this disables verifies that a change detection pass does not result in additional changes to any bindings (also known as unidirectional data flow).

Using this method is discouraged as the Angular CLI will set production mode when using the `optimization` option.

Jump to details

----
url: https://angular.dev/api/ssr/node/CommonEngineOptions
----

```
    
      interface CommonEngineOptions {}
    
    
```

A method that when invoked returns a promise that returns an [`ApplicationRef`](/api/core/ApplicationRef) instance once resolved or an NgModule.

A set of platform level providers for all requests.

Enable request performance profiling data collection and printing the results in the server console.

A set of hostnames that are allowed to access the server.

Jump to details

----
url: https://angular.dev/api/common/isPlatformServer
----

```
function isPlatformServer(platformId: Object): boolean;
```

@paramplatformId`Object`

@returns`boolean`

Jump to details

----
url: https://angular.dev/api/forms/signals/provideSignalFormsConfig
----

```
function provideSignalFormsConfig(config: SignalFormsConfig): Provider[];
```

@paramconfig`SignalFormsConfig`

@returns`Provider[]`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/8-input
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/7-event-handling)[](/tutorials/learn-angular/9-output)

[](/tutorials/learn-angular/7-event-handling)[](/tutorials/learn-angular/9-output)

Sometimes app development requires you to send data into a component. This data can be used to customize a component or perhaps send information from a parent component to a child component.

Angular uses a concept called `input`. This is similar to `props` in other frameworks. To create an `input` property, use the [`input()`](/api/core/input) function.

**NOTE:** Learn more about [accepting data with input properties in the inputs guide](/guide/components/inputs).

In this activity, you'll learn how to use the [`input()`](/api/core/input) function to send information to components.

***

To create an `input` property, add the [`input()`](/api/core/input) function to initialize a property of a component class:

### user.ts

```
class User {
  occupation = input<string>();
}
```

When you are ready to pass in a value through an `input`, values can be set in templates using the attribute syntax. Here's an example:

### app.ts

```
@Component({
  ...
  template: `<app-user occupation="Angular Developer"></app-user>`
})
export class App {}
```

The `input` function returns an [`InputSignal`](/api/core/InputSignal). You can read the value by calling the signal.

### user.ts

```
@Component({
  ...
  template: `<p>The user's occupation is {{occupation()}}</p>`
})
```

1. ### [Define an `input()` property](#define-an-input-property)

   Update the code in `user.ts` to define an `input` property on the `User` called `name` and specify the `string` type. For now, don't set an initial value and invoke [`input()`](/api/core/input) without arguments. Be sure to update the template to invoke and interpolate the `name` property at the end of the sentence.

2. ### [Pass a value to the `input` property](#pass-a-value-to-the-input-property)

   Update the code in `app.ts` to send in the `name` property with a value of `"Simran"`.

   When the code has been successfully updated, the app will display `The user's name is Simran`.

While this is great, it is only one direction of the component communication. What if you want to send information and data to a parent component from a child component? Check out the next lesson to find out.

P.S. you are doing great - keep going 🎉

----
url: https://angular.dev/api/core/PLATFORM_ID
----

```
    
      const PLATFORM_ID: InjectionToken<Object>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ViewChildren
----

```
@ViewChildren(
  selector: ProviderToken<unknown> | Function | string,
  opts?: { read?: any; emitDistinctChangesOnly?: boolean },
);
```

## [Description](#description)

Property decorator that configures a view query.

Use to get the [`QueryList`](/api/core/QueryList) of elements or directives from the view DOM. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value.

View queries are set before the `ngAfterViewInit` callback is called.

**Metadata Properties**:

* **selector** - The directive type or the name used for querying.
* **read** - Used to read a different token from the queried elements.
* **emitDistinctChangesOnly** - The `QueryList#changes` observable will emit new values only if the QueryList result has changed. When `false` the `changes` observable might emit even if the QueryList has not changed. **Note:** This config option is **deprecated**, it will be permanently set to `true` and removed in future versions of Angular.

The following selectors are supported.

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* A template reference variable as a string (e.g. query `<my-component #cmp></my-component>` with `@ViewChildren('cmp')`)
* Any provider defined in the child component tree of the current component (e.g. `@ViewChildren(SomeService) someService!: SomeService`)
* Any provider defined through a string token (e.g. `@ViewChildren('someToken') someTokenVal!: any`)
* A [`TemplateRef`](/api/core/TemplateRef) (e.g. query `<ng-template></ng-template>` with `@ViewChildren(TemplateRef) template;`)

In addition, multiple string selectors can be separated with a comma (e.g. `@ViewChildren('cmp1,cmp2')`)

The following values are supported by `read`:

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* Any provider defined on the injector of the component that is matched by the `selector` of this query
* Any provider defined through a string token (e.g. `{provide: 'token', useValue: 'val'}`)
* [`TemplateRef`](/api/core/TemplateRef), [`ElementRef`](/api/core/ElementRef), [`ViewContainerRef`](/api/core/ViewContainerRef), and [`Injector`](/api/core/Injector)

## [Usage Notes](#usage-notes)

```
import {AfterViewInit, Component, Directive, QueryList, ViewChildren} from '@angular/core';

@Directive({
  selector: 'child-directive',
})
class ChildDirective {}

@Component({
  selector: 'someCmp',
  templateUrl: 'someCmp.html',
})
class SomeCmp implements AfterViewInit {
  @ViewChildren(ChildDirective) viewChildren!: QueryList<ChildDirective>;

  ngAfterViewInit() {
    // viewChildren is set
  }
}
```

### [Another example](#another-example)

```
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  Component,
  Directive,
  input,
  QueryList,
  signal,
  ViewChildren,
} from '@angular/core';

@Directive({
  selector: 'pane',
})
export class Pane {
  id = input.required<string>();
}

@Component({
  selector: 'example-app',
  imports: [Pane],
  template: `
    <pane id="1" />
    <pane id="2" />
    @if (shouldShow()) {
      <pane id="3" />
    }

    <button (click)="show()">Show 3</button>

    <div>panes: {{ serializedPanes }}</div>
  `,
  changeDetection: ChangeDetectionStrategy.Eager,
})
export class ViewChildrenComp implements AfterViewInit {
  @ViewChildren(Pane) panes!: QueryList<Pane>;
  serializedPanes: string = '';

  shouldShow = signal(false);

  show() {
    this.shouldShow.set(true);
  }

  ngAfterViewInit() {
    this.calculateSerializedPanes();
    this.panes.changes.subscribe(() => {
      this.calculateSerializedPanes();
    });
  }

  calculateSerializedPanes() {
    setTimeout(() => {
      this.serializedPanes = this.panes.map((p) => p.id()).join(', ');
    }, 0);
  }
}
```

Jump to details

----
url: https://angular.dev/api/animations/AnimationEvent
----

```
    
      interface AnimationEvent {}
    
    
```

The name of the state from which the animation is triggered.

The name of the state in which the animation completes.

The time it takes the animation to complete, in milliseconds.

The animation phase in which the callback was invoked, one of "start" or "done".

The element to which the animation is attached.

Internal.

Internal.

## [Description](#description)

An instance of this class is returned as an event parameter when an animation callback is captured for an animation either during the start or done phase.

```
@Component({
  host: {
    '[@myAnimationTrigger]': 'someExpression',
    '(@myAnimationTrigger.start)': 'captureStartEvent($event)',
    '(@myAnimationTrigger.done)': 'captureDoneEvent($event)',
  },
  animations: [
    trigger("myAnimationTrigger", [
       // ...
    ])
  ]
})
class MyComponent {
  someExpression: any = false;
  captureStartEvent(event: AnimationEvent) {
    // the toState, fromState and totalTime data is accessible from the event variable
  }

  captureDoneEvent(event: AnimationEvent) {
    // the toState, fromState and totalTime data is accessible from the event variable
  }
}
```

Jump to details

----
url: https://angular.dev/api/common/http/withXhr
----

```
function withXhr(): HttpFeature<HttpFeatureKind.Xhr>;
```

@returns`HttpFeature<HttpFeatureKind.Xhr>`

## [Description](#description)

Configures the current [`HttpClient`](/api/common/http/HttpClient) instance to make requests using the Xhr API.

Use this feature if you want to report progress on uploads as the Xhr API supports it.

Do not use [`withXhr`](/api/common/http/withXhr) in server-side rendering (SSR) environments. XHR support on the server is **deprecated** and is intended to be removed in Angular 23 because the underlying `xhr2` library does not safely handle redirects (e.g. it can forward `Authorization` headers on cross-origin redirects and is susceptible to denial-of-service via redirect loops).

Jump to details

----
url: https://angular.dev/api/core/HostListener
----

```
@HostListener(eventName: string, args?: string[]);
```

## [Description](#description)

Decorator that declares a DOM event to listen for, and provides a handler method to run when that event occurs.

Angular invokes the supplied handler method when the host element emits the specified event, and updates the bound element with the result.

If the handler method returns false, applies `preventDefault` on the bound element.

## [Usage Notes](#usage-notes)

**NOTE:** **Always** prefer using the [`host` property](guide/components/host-elements#binding-to-the-host-element) over [`@HostListener`](/api/core/HostListener). This decorator exist exclusively for backwards compatibility.

The following example declares a directive that attaches a click listener to a button and counts clicks.

```
@Directive({selector: 'button[counting]'})
class CountClicks {
  numberOfClicks = 0;

  @HostListener('click', ['$event.target'])
  onClick(btn) {
    console.log('button', btn, 'number of clicks:', this.numberOfClicks++);
  }
}

@Component({
  selector: 'app',
  template: '<button counting>Increment</button>',
})
class App {}
```

The following example registers another DOM event handler that listens for `Enter` key-press events on the global `window`.

```
import { HostListener, Component } from "@angular/core";

@Component({
  selector: 'app',
  template: `
   <h1>Hello, you have pressed enter {{counter}} number of times!</h1>
     Press enter  key to increment the counter.
   <button (click)="resetCounter()">Reset Counter</button>`
})
class AppComponent {
  counter = 0;
  @HostListener('window:keydown.enter', ['$event'])
  handleKeyDown(event: KeyboardEvent) {
    this.counter++;
  }
  resetCounter() {
    this.counter = 0;
  }
}
```

The list of valid key names for `keydown` and `keyup` events can be found here: <https://www.w3.org/TR/DOM-Level-3-Events-key/#named-key-attribute-values>

**NOTE:** that keys can also be combined, e.g. `@HostListener('keydown.shift.a')`.

Key names are matched against `KeyboardEvent.key`, which depends on the user's keyboard layout and input language. To match a physical key regardless of layout, use the `code` modifier instead, e.g. `@HostListener('keydown.code.KeyA')`. See [Using key modifiers](guide/templates/event-listeners#using-key-modifiers) for details.

The global target names that can be used to prefix an event name are `document:`, `window:` and `body:`.

Jump to details

----
url: https://angular.dev/errors/NG1001
----

To make the metadata extraction in the Angular compiler faster, the decorators [`@NgModule`](/api/core/NgModule), [`@Pipe`](/api/core/Pipe), [`@Component`](/api/core/Component), [`@Directive`](/api/core/Directive), and [`@Injectable`](/api/core/Injectable) accept only object literals as arguments.

This is an [intentional change in Ivy](https://github.com/angular/angular/issues/30840#issuecomment-498869540), which enforces stricter argument requirements for decorators than View Engine. Ivy requires this approach because it compiles decorators by moving the expressions into other locations in the class output.

## [Debugging the error](#debugging-the-error)

Move all declarations:

```
const moduleDefinition = {…}

@NgModule(moduleDefinition)
export class AppModule {
  constructor() {}
}
```

into the decorator:

```
@NgModule({…})
export class AppModule {
  constructor() {}
}
```

----
url: https://angular.dev/api/aria/toolbar/Toolbar
----

```
    
      class Toolbar implements OnDestroy {}
    
    
```

A reference to the host element.

Text direction.

Whether the toolbar is vertically or horizontally oriented.

Whether to allow disabled items to receive focus. When `true`, disabled items are focusable but not interactive. When `false`, disabled items are skipped during navigation.

Whether the toolbar is disabled.

Whether focus should wrap when navigating.

@returns`void`

## [Description](#description)

A toolbar widget container for a group of interactive widgets, such as buttons or radio groups. It provides a single point of reference for keyboard navigation and focus management. It supports various orientations and disabled states.

```
<div ngToolbar orientation="horizontal" [wrap]="true">
  <button ngToolbarWidget>Save</button>
  <button ngToolbarWidget>Print</button>

  <div ngToolbarWidgetGroup>
    <button ngToolbarWidget>Left</button>
    <button ngToolbarWidget>Center</button>
    <button ngToolbarWidget>Right</button>
  </div>
</div>
```

Jump to details

----
url: https://angular.dev/api/common/DATE_PIPE_DEFAULT_OPTIONS
----

```
    
      const DATE_PIPE_DEFAULT_OPTIONS: InjectionToken<DatePipeConfig>;
    
    
```

## [Usage Notes](#usage-notes)

Various date pipe default values can be overwritten by providing this token with the value that has this interface.

For example:

Override the default date format by providing a value using the token:

```
providers: [
  {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}
]
```

Override the default timezone by providing a value using the token:

```
providers: [
  {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}
]
```

Jump to details

----
url: https://angular.dev/api/common/http/HTTP_INTERCEPTORS
----

```
    
      const HTTP_INTERCEPTORS: InjectionToken<readonly HttpInterceptor[]>;
    
    
```

Jump to details

----
url: https://angular.dev/api/platform-browser/withNoIncrementalHydration
----

## [API](/api/platform-browser/withNoIncrementalHydration#api)

```
function withNoIncrementalHydration(): HydrationFeature<HydrationFeatureKind.NoIncrementalHydration>;
```

@returns`HydrationFeature<HydrationFeatureKind.NoIncrementalHydration>`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/23-pipes-format-data
----

[](/tutorials/learn-angular/22-pipes)[](/tutorials/learn-angular/24-create-a-pipe)

[](/tutorials/learn-angular/22-pipes)[](/tutorials/learn-angular/24-create-a-pipe)

You can take your use of pipes even further by configuring them. Pipes can be configured by passing options to them.

**NOTE:** Learn more about [formatting data with pipes in the in-depth guide](/guide/templates/pipes).

In this activity, you will work with some pipes and pipe parameters.

***

To pass parameters to a pipe, use the `:` syntax followed by the parameter value. Here's an example:

```
template: `{{ date | date: 'medium' }}`;
```

The output is `Jun 15, 2015, 9:43:11 PM`.

Time to customize some pipe output:

1. ### [Format a number with `DecimalPipe`](#format-a-number-with-decimalpipe)

   In `app.ts`, update the template to include parameter for the `decimal` pipe.

   ```
   template: ` ...
   <li>Number with "decimal" {{ num | number: '3.2-2' }}</li>
   `
   ```

   **NOTE:** What's that format? The parameter for the [`DecimalPipe`](/api/common/DecimalPipe) is called `digitsInfo`, this parameter uses the format: `{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}`

2. ### [Format a date with `DatePipe`](#format-a-date-with-datepipe)

   Now, update the template to use the `date` pipe.

   ```
   template: ` ...
   <li>Date with "date" {{ birthday | date: 'medium' }}</li>
   `
   ```

   For extra fun, try some different parameters for `date`. More information can be found in the [Angular docs](guide/templates/pipes).

3. ### [Format a currency with `CurrencyPipe`](#format-a-currency-with-currencypipe)

   For your last task, update the template to use the `currency` pipe.

   ```
   template: ` ...
   <li>Currency with "currency" {{ cost | currency }}</li>
   `
   ```

   You can also try different parameters for `currency`. More information can be found in the [Angular docs](guide/templates/pipes).

Great work with pipes. You've made some great progress so far.

There are even more built-in pipes that you can use in your applications. You can find the list in the [Angular documentation](guide/templates/pipes).

In the case that the built-in pipes don't cover your needs, you can also create a custom pipe. Check out the next lesson to find out more.

----
url: https://angular.dev/api/core/provideIdleServiceWith
----

```
function provideIdleServiceWith(
  useExisting: AbstractType<IdleService> | InjectionToken<IdleService>,
): EnvironmentProviders;
```

@paramuseExisting`AbstractType<IdleService> | InjectionToken<IdleService>`

@returns`EnvironmentProviders`

## [Description](#description)

Configures Angular to use the given DI token as its [`IdleService`](/api/core/IdleService).

The given token must be available for injection from the root injector, and the injected value must implement the [`IdleService`](/api/core/IdleService) interface.

Jump to details

----
url: https://angular.dev/api/core/Input
----

```
@Input(
  arg?:
    | string
    | {
        alias?: string;
        required?: boolean;
        transform?: (value: any) => any;
        isSignal?: boolean;
      },
);
```

## [Usage Notes](#usage-notes)

You can supply an optional name to use in templates when the component is instantiated, that maps to the name of the bound property. By default, the original name of the bound property is used for input binding.

The following example creates a component with two input properties, one of which is given a special binding name.

```
import { Component, Input, numberAttribute, booleanAttribute } from '@angular/core';
@Component({
  selector: 'bank-account',
  template: `
    Bank Name: {{bankName}}
    Account Id: {{id}}
    Account Status: {{status ? 'Active' : 'InActive'}}
  `
})
class BankAccount {
  // This property is bound using its original name.
  // Defining argument required as true inside the Input Decorator
  // makes this property declaration as mandatory
  @Input({ required: true }) bankName!: string;
  // Argument alias makes this property value is bound to a different property name
  // when this component is instantiated in a template.
  // Argument transform convert the input value from string to number
  @Input({ alias:'account-id', transform: numberAttribute }) id: number;
  // Argument transform the input value from string to boolean
  @Input({ transform: booleanAttribute }) status: boolean;
  // this property is not bound, and is not automatically updated by Angular
  normalizedBankName: string;
}

@Component({
  selector: 'app',
  template: `
    <bank-account bankName="RBC" account-id="4747" status="true"></bank-account>
  `
})
class App {}
```

Jump to details

----
url: https://angular.dev/api/core/testing/ComponentFixtureNoNgZone
----

```
    
      const ComponentFixtureNoNgZone: InjectionToken<boolean>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/validateTree
----

```
function validateTree<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  logic: NoInfer<TreeValidator<TValue, TPathKind>>,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to add the validation logic to.

@paramlogic`NoInfer<TreeValidator<TValue, TPathKind>>`

A [`TreeValidator`](/api/forms/signals/TreeValidator) that returns the current validation errors. Errors returned by the validator may specify a target field to indicate an error on a child field.

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/InputSignal
----

```
    
      interface InputSignal<T> extends InputSignalWithTransform<T, T> {}
    
    
```

## [Description](#description)

[`InputSignal`](/api/core/InputSignal) represents a special [`Signal`](/api/core/Signal) for a directive/component input.

An input signal is similar to a non-writable signal except that it also carries additional type-information for transforms, and that Angular internally updates the signal whenever a new value is bound.

Jump to details

----
url: https://angular.dev/api/forms/signals/TransformedValueOptions
----

```
    
      interface TransformedValueOptions<TValue, TRaw> {}
    
    
```

Parse the raw value into the model value.

Should return an object containing the parsed result, which may contain:

* `value`: The parsed model value. If `undefined`, the model will not be updated.
* `error`: Any parse errors encountered. If `undefined`, no errors are reported.

Format the model value into the raw value.

Jump to details

----
url: https://angular.dev/api/router/RunGuardsAndResolvers
----

```
    
      type RunGuardsAndResolvers = | 'pathParamsChange'  | 'pathParamsOrQueryParamsChange'  | 'paramsChange'  | 'paramsOrQueryParamsChange'  | 'always'  | ((from: ActivatedRouteSnapshot, to: ActivatedRouteSnapshot) => boolean)
    
    
```

## [Description](#description)

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CDK_DRAG_PLACEHOLDER
----

```
    
      const CDK_DRAG_PLACEHOLDER: any;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpResourceRef
----

```
    
      interface HttpResourceRef<T> extends WritableResource<T>, ResourceRef<T> {
}
    
    
```

Signal of the response headers, when available.

Signal of the response status code, when available.

Signal of the latest progress update, if the request was made with `reportProgress: true`.

@paramthis`T extends undefined ? this : never`

@returns`this is HttpResourceRef<Exclude<T, undefined>>`

@returns`boolean`

@returns`void`

Convenience wrapper for `value.set`.

@paramvalue`T`

@returns`void`

Convenience wrapper for `value.update`.

@paramupdater`(value: T) => T`

@returns`void`

@returns`Resource<T>`

Instructs the resource to re-load any asynchronous dependency it may have.

Note that the resource will not enter its reloading state until the actual backend request is made.

@returns`boolean`

true if a reload was initiated, false if a reload was unnecessary or unsupported

The current status of the `Resource`, which describes what the resource is currently doing and what can be expected of its `value`.

When in the `error` state, this returns the last known error from the `Resource`.

Whether this resource is loading a new value (or reloading the existing one).

The current state of this resource, represented as a `ResourceSnapshot`.

## [Description](#description)

A [`WritableResource`](/api/core/WritableResource) that represents the results of a reactive HTTP request.

`HttpResource`s are backed by [`HttpClient`](/api/common/http/HttpClient), including support for interceptors, testing, and the other features of the [`HttpClient`](/api/common/http/HttpClient) API.

Jump to details

----
url: https://angular.dev/api/router/ChildActivationEnd
----

```
    
      class ChildActivationEnd {}
    
    
```

@paramsnapshot`ActivatedRouteSnapshot`

@returns`ChildActivationEnd`

Jump to details

----
url: https://angular.dev/cli/build
----

```
ng build [project] [options]
```

```
ng b [project] [options]
```

The command can be used to build a project of type "application" or "library". When used to build a library, a different builder is invoked, and only the `ts-config`, `configuration`, `poll` and `watch` options are applied. All other options apply only to building applications.

The application builder uses the [esbuild](https://esbuild.github.io/) build tool, with default configuration options specified in the workspace configuration file (`angular.json`) or with a named alternative configuration. A "development" configuration is created by default when you use the CLI to create the project, and you can use that configuration by specifying the `--configuration development`.

The configuration options generally correspond to the command options. You can override individual configuration defaults by specifying the corresponding options on the command line. The command can accept option names given in dash-case. Note that in the configuration file, you must specify names in camelCase.

Some additional options can only be set through the configuration file, either by direct editing or with the `ng config` command. These include `assets`, `styles`, and `scripts` objects that provide runtime-global resources to include in the project. Resources in CSS, such as images and fonts, are automatically written and fingerprinted at the root of the output folder.

For further details, see [Workspace Configuration](reference/configs/workspace-config).

## [Arguments](#arguments)

`project`

The name of the project to build. Can be an application or a library.

Value Type`string`

## [Options](#options)

`allowed-common-js-dependencies`

A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.

Value Type`array`

`aot`

Build using Ahead of Time compilation.

Value Type`boolean`Default`true`

`app-shell`

Generates an application shell during build time.

Value Type`boolean`

`base-href`

Base url for the application being built.

Value Type`string`

`browser`

The full path for the browser entry point to the application, relative to the current workspace.

Value Type`string`

`clear-screen`

Automatically clear the terminal screen during rebuilds.

Value Type`boolean`Default`false`

`conditions`

Custom package resolution conditions used to resolve conditional exports/imports. Defaults to \['module', 'development'/'production']. The following special conditions are always present if the requirements are satisfied: 'default', 'import', 'require', 'browser', 'node'.

Value Type`array`

`configuration`

Alias`c`

One or more named builder configurations as a comma-separated list as specified in the "configurations" section in angular.json. The builder uses the named configurations to run the given target. For more information, see [https://angular.dev/reference/configs/workspace-config#alternate-build-configurations](/reference/configs/workspace-config#alternate-build-configurations).

Value Type`string`

`cross-origin`

Define the crossorigin attribute setting of elements that provide CORS support.

Value Type`string`Allowed Values`anonymous`, `none`, `use-credentials`Default`none`

`define`

Defines global identifiers that will be replaced with a specified constant value when found in any JavaScript or TypeScript code including libraries. The value will be used directly. String values must be put in quotes. Identifiers within Angular metadata such as Component Decorators will not be replaced.

Value Type`array`

`delete-output-path`

Delete the output path before building.

Value Type`boolean`Default`true`

`deploy-url`

Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations.

Value Type`string`

`external-dependencies`

Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime. Note: `@foo/bar` marks all paths within the `@foo/bar` package as external, including sub-paths like `@foo/bar/baz`.

Value Type`array`

`extract-licenses`

Extract all licenses in a separate file.

Value Type`boolean`Default`true`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

`i18n-duplicate-translation`

How to handle duplicate translations for i18n.

Value Type`string`Allowed Values`error`, `ignore`, `warning`Default`warning`

`i18n-missing-translation`

How to handle missing translations for i18n.

Value Type`string`Allowed Values`error`, `ignore`, `warning`Default`warning`

`index`

Configures the generation of the application's HTML index.

Value Type`string`

`inline-style-language`

The stylesheet language to use for the application's inline component styles.

Value Type`string`Allowed Values`css`, `less`, `sass`, `scss`Default`css`

`localize`

Translate the bundles in one or more locales.

Value Type`boolean`

`named-chunks`

Use file name for lazy loaded chunks.

Value Type`boolean`Default`false`

`optimization`

Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see [https://angular.dev/reference/configs/workspace-config#optimization-configuration](/reference/configs/workspace-config#optimization-configuration).

Value Type`boolean`Default`true`

`output-hashing`

Define the output filename cache-busting hashing mode.

* `none`: No hashing.
* `all`: Hash for all output bundles.
* `media`: Hash for all output media (e.g., images, fonts, etc. that are referenced in CSS files).
* `bundles`: Hash for output of lazy and main bundles.

Value Type`string`Allowed Values`all`, `bundles`, `media`, `none`Default`none`

`output-mode`

Defines the type of build output artifact. 'static': Generates a static site build artifact for deployment on any static hosting service. 'server': Generates a server application build artifact, required for applications using hybrid rendering or APIs.

Value Type`string`Allowed Values`server`, `static`

`output-path`

Specify the output path relative to workspace root.

Value Type`string`

`poll`

Enable and define the file watching poll time period in milliseconds.

Value Type`number`

`polyfills`

A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.

Value Type`array`

`prerender`

Prerender (SSG) pages of your application during build time.

Value Type`boolean`

`preserve-symlinks`

Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set.

Value Type`boolean`

`progress`

Log progress to the console while building.

Value Type`boolean`Default`true`

`scripts`

Global scripts to be included in the build.

Value Type`array`

`server`

The full path for the server entry point to the application, relative to the current workspace.

Value Type`string`

`service-worker`

Generates a service worker configuration.

Value Type`string`

`source-map`

Output source maps for scripts and styles. For more information, see [https://angular.dev/reference/configs/workspace-config#source-map-configuration](/reference/configs/workspace-config#source-map-configuration).

Value Type`boolean`Default`false`

`ssr`

Server side render (SSR) pages of your application during runtime.

Value Type`boolean`Default`false`

`stats-json`

Generates a 'stats.json' file which can be analyzed with <https://esbuild.github.io/analyze/>.

Value Type`boolean`Default`false`

`styles`

Global styles to be included in the build.

Value Type`array`

`subresource-integrity`

Enables the use of subresource integrity validation.

Value Type`boolean`Default`false`

`ts-config`

The full path for the TypeScript configuration file, relative to the current workspace.

Value Type`string`

`verbose`

Adds more details to output logging.

Value Type`boolean`Default`false`

`watch`

Run build when files change.

Value Type`boolean`Default`false`

`web-worker-ts-config`

TypeScript configuration for Web Worker modules.

Value Type`string`

Jump to details

----
url: https://angular.dev/guide/testing/utility-apis
----

**NOTE:** While this guide is being updated for Vitest, some descriptions and examples of utility APIs are currently presented within the context of Karma/Jasmine. We are actively working to provide Vitest equivalents and updated guidance where applicable.

This page describes the most useful Angular testing features.

The Angular testing utilities include the [`TestBed`](/api/core/testing/TestBed), the [`ComponentFixture`](/api/core/testing/ComponentFixture), and a handful of functions that control the test environment. The [`TestBed`](#testbed-class-summary) and [`ComponentFixture`](#the-componentfixture) classes are covered separately.

Here's a summary of the stand-alone functions, in order of likely utility:

| Function                                     | Details                                                                                                                                                                                                                                                                                                                                     |
| :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| \[`inject`]                                  | Injects one or more services from the current [`TestBed`](/api/core/testing/TestBed) injector into a test function. It cannot inject a service provided by the component itself. See discussion of the [debugElement.injector](guide/testing/components-scenarios#get-injected-services).                                                   |
| [`getTestBed`](/api/core/testing/getTestBed) | Gets the current instance of the [`TestBed`](/api/core/testing/TestBed). Usually unnecessary because the static class methods of the [`TestBed`](/api/core/testing/TestBed) class are typically sufficient. The [`TestBed`](/api/core/testing/TestBed) instance exposes a few rarely used members that are not available as static methods. |

For handling complex asynchronous scenarios or testing legacy Zone.js-based applications, see the [Zone.js Testing Utilities](guide/testing/zone-js-testing-utilities) guide.

## [`TestBed` class summary](#testbed-class-summary)

The [`TestBed`](/api/core/testing/TestBed) class is one of the principal Angular testing utilities. Its API is quite large and can be overwhelming until you've explored it, a little at a time. Read the early part of this guide first to get the basics before trying to absorb the full API.

The module definition passed to `configureTestingModule` is a subset of the [`@NgModule`](/api/core/NgModule) metadata properties.

```
type TestModuleMetadata = {
  providers?: any[];
  declarations?: any[];
  imports?: any[];
  schemas?: Array<SchemaMetadata | any[]>;
};
```

Each override method takes a `MetadataOverride<T>` where `T` is the kind of metadata appropriate to the method, that is, the parameter of an [`@NgModule`](/api/core/NgModule), [`@Component`](/api/core/Component), [`@Directive`](/api/core/Directive), or [`@Pipe`](/api/core/Pipe).

```
type MetadataOverride<T> = {
  add?: Partial<T>;
  remove?: Partial<T>;
  set?: Partial<T>;
};
```

The [`TestBed`](/api/core/testing/TestBed) API consists of static class methods that either update or reference a *global* instance of the [`TestBed`](/api/core/testing/TestBed).

Internally, all static methods cover methods of the current runtime [`TestBed`](/api/core/testing/TestBed) instance, which is also returned by the [`getTestBed()`](/api/core/testing/getTestBed) function.

Call [`TestBed`](/api/core/testing/TestBed) methods *within* a `beforeEach()` to ensure a fresh start before each individual test.

Here are the most important static methods, in order of likely utility.

| Methods                  | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configureTestingModule` | The testing shims establish the [initial test environment](guide/testing) and a default testing module. The default testing module is configured with basic declaratives and some Angular service substitutes that every tester needs. Call `configureTestingModule` to refine the testing module configuration for a particular set of tests by adding and removing imports, declarations (of components, directives, and pipes), and providers.                                                                                                                                                                                                                          |
| `compileComponents`      | Compile the testing module asynchronously after you've finished configuring it. You **must** call this method if *any* of the testing module components have asynchronously loaded resources (like @defer blocks). After calling `compileComponents`, the [`TestBed`](/api/core/testing/TestBed) configuration is frozen for the duration of the current spec.                                                                                                                                                                                                                                                                                                             |
| `createComponent<T>`     | Create an instance of a component of type `T` based on the current [`TestBed`](/api/core/testing/TestBed) configuration. After calling [`createComponent`](/api/core/createComponent), the [`TestBed`](/api/core/testing/TestBed) configuration is frozen for the duration of the current spec.                                                                                                                                                                                                                                                                                                                                                                            |
| `overrideComponent`      | Replace metadata for the given component class, which could be nested deeply within an inner module.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `overrideDirective`      | Replace metadata for the given directive class, which could be nested deeply within an inner module.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `overridePipe`           | Replace metadata for the given pipe class, which could be nested deeply within an inner module.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `overrideModule`         | Replace metadata for the given [`NgModule`](/api/core/NgModule). Recall that modules can import other modules. The `overrideModule` method can reach deeply into the current testing module to modify one of these inner modules.                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `inject`                 | Retrieve a service from the current [`TestBed`](/api/core/testing/TestBed) injector. The `inject` function is often adequate for this purpose. But `inject` throws an error if it can't provide the service. What if the service is optional? The [`TestBed.inject()`](/api/core/testing/TestBed#inject) method takes an optional second parameter, the object to return if Angular can't find the provider (`null` in this example): `expect(TestBed.inject(NotProvided, null)).toBeNull();` After calling [`TestBed.inject`](/api/core/testing/TestBed#inject), the [`TestBed`](/api/core/testing/TestBed) configuration is frozen for the duration of the current spec. |
| `initTestEnvironment`    | Initialize the testing environment for the entire test run. The testing shims call it for you so there is rarely a reason for you to call it yourself. Call this method *exactly once*. To change this default in the middle of a test run, call `resetTestEnvironment` first. Specify a default Angular testing module and a [`PlatformRef`](/api/core/PlatformRef). Alternatives for non-browser platforms are available in the general form `@angular/platform-<platform_name>/testing`.                                                                                                                                                                                |
| `resetTestEnvironment`   | Reset the initial test environment, including the default testing module.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

A few of the [`TestBed`](/api/core/testing/TestBed) instance methods are not covered by static [`TestBed`](/api/core/testing/TestBed) *class* methods. These are rarely needed.

## [The `ComponentFixture`](#the-componentfixture)

The [`TestBed.createComponent<T>`](/api/core/testing/TestBed#createComponent) creates an instance of the component `T` and returns a strongly typed [`ComponentFixture`](/api/core/testing/ComponentFixture) for that component.

The [`ComponentFixture`](/api/core/testing/ComponentFixture) properties and methods provide access to the component, its DOM representation, and aspects of its Angular environment.

### [`ComponentFixture` properties](#componentfixture-properties)

Here are the most important properties for testers, in order of likely utility.

| Properties          | Details                                                                                                                                                                                                                                                                                                      |
| :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `componentInstance` | The instance of the component class created by [`TestBed.createComponent`](/api/core/testing/TestBed#createComponent).                                                                                                                                                                                       |
| `debugElement`      | The [`DebugElement`](/api/core/DebugElement) associated with the root element of the component. The `debugElement` provides insight into the component and its DOM element during test and debugging. It's a critical property for testers. The most interesting members are covered [below](#debugelement). |
| `nativeElement`     | The native DOM element at the root of the component.                                                                                                                                                                                                                                                         |
| `changeDetectorRef` | The [`ChangeDetectorRef`](/api/core/ChangeDetectorRef) of the component's host view, not of the component itself. See [Using Zoneless in `TestBed`](guide/zoneless#using-zoneless-in-testbed).                                                                                                               |

### [`ComponentFixture` methods](#componentfixture-methods)

The *fixture* methods cause Angular to perform certain tasks on the component tree. Call these methods to trigger Angular behavior in response to simulated user action.

Here are the most useful methods for testers.

| Methods             | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `detectChanges`     | Trigger a change detection cycle for the component. Call it to initialize the component (it calls `ngOnInit`) and after your test code, change the component's data bound property values. Angular can't see that you've changed `personComponent.name` and won't update the `name` binding until you call `detectChanges`. Runs `checkNoChanges` afterwards to confirm that there are no circular updates unless called as `detectChanges(false)`;                                                                                                                                          |
| `autoDetectChanges` | Set this to `true` when you want the fixture to detect changes automatically. When autodetect is `true`, the test fixture calls `detectChanges` immediately after creating the component. Then it listens for pertinent zone events and calls `detectChanges` accordingly. When your test code modifies component property values directly, you probably still have to call `fixture.detectChanges` to trigger data binding updates. The default is `true` with zoneless change detection (the [`TestBed`](/api/core/testing/TestBed) default) and `false` with Zone-based change detection. |
| `checkNoChanges`    | Do a change detection run to make sure there are no pending changes. Throws an exceptions if there are.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `isStable`          | If the fixture is currently *stable*, returns `true`. If there are async tasks that have not completed, returns `false`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `whenStable`        | Returns a promise that resolves when the fixture is stable. To resume testing after completion of asynchronous activity or asynchronous change detection, hook that promise. See [whenStable](guide/testing/components-scenarios#whenstable).                                                                                                                                                                                                                                                                                                                                                |
| `destroy`           | Trigger component destruction.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |

#### [`DebugElement`](#debugelement)

The [`DebugElement`](/api/core/DebugElement) provides crucial insights into the component's DOM representation.

From the test root component's [`DebugElement`](/api/core/DebugElement) returned by `fixture.debugElement`, you can walk (and query) the fixture's entire element and component subtrees.

Here are the most useful [`DebugElement`](/api/core/DebugElement) members for testers, in approximate order of utility:

| Members               | Details                                                                                                                                                                                                                                                                                                                                                                                                                       |
| :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nativeElement`       | The corresponding DOM element in the browser                                                                                                                                                                                                                                                                                                                                                                                  |
| `query`               | Calling `query(predicate: Predicate<DebugElement>)` returns the first [`DebugElement`](/api/core/DebugElement) that matches the predicate at any depth in the subtree.                                                                                                                                                                                                                                                        |
| `queryAll`            | Calling `queryAll(predicate: Predicate<DebugElement>)` returns all `DebugElements` that matches the predicate at any depth in subtree.                                                                                                                                                                                                                                                                                        |
| `injector`            | The host dependency injector. For example, the root element's component instance injector.                                                                                                                                                                                                                                                                                                                                    |
| `componentInstance`   | The element's own component instance, if it has one.                                                                                                                                                                                                                                                                                                                                                                          |
| `context`             | An object that provides parent context for this element. Often an ancestor component instance that governs this element. When an element is repeated within `@for` block, the context is an `RepeaterContext` whose `$implicit` property is the value of the row instance value. For example, the `hero` in `@for(hero of heroes; ...)`.                                                                                      |
| `children`            | The immediate [`DebugElement`](/api/core/DebugElement) children. Walk the tree by descending through `children`. [`DebugElement`](/api/core/DebugElement) also has `childNodes`, a list of [`DebugNode`](/api/core/DebugNode) objects. [`DebugElement`](/api/core/DebugElement) derives from [`DebugNode`](/api/core/DebugNode) objects and there are often more nodes than elements. Testers can usually ignore plain nodes. |
| `parent`              | The [`DebugElement`](/api/core/DebugElement) parent. Null if this is the root element.                                                                                                                                                                                                                                                                                                                                        |
| `name`                | The element tag name, if it is an element.                                                                                                                                                                                                                                                                                                                                                                                    |
| `triggerEventHandler` | Triggers the event by its name if there is a corresponding listener in the element's `listeners` collection. The second parameter is the *event object* expected by the handler. If the event lacks a listener or there's some other problem, consider calling `nativeElement.dispatchEvent(eventObject)`.                                                                                                                    |
| `listeners`           | The callbacks attached to the component's [`@Output`](/api/core/Output) properties and/or the element's event properties.                                                                                                                                                                                                                                                                                                     |
| `providerTokens`      | This component's injector lookup tokens. Includes the component itself plus the tokens that the component lists in its `providers` metadata.                                                                                                                                                                                                                                                                                  |
| `source`              | Where to find this element in the source component template.                                                                                                                                                                                                                                                                                                                                                                  |
| `references`          | Dictionary of objects associated with template local variables (for example, `#foo`), keyed by the local variable name.                                                                                                                                                                                                                                                                                                       |

The [`DebugElement.query(predicate)`](/api/core/DebugElement#query) and [`DebugElement.queryAll(predicate)`](/api/core/DebugElement#queryAll) methods take a predicate that filters the source element's subtree for matching [`DebugElement`](/api/core/DebugElement).

The predicate is any method that takes a [`DebugElement`](/api/core/DebugElement) and returns a *truthy* value. The following example finds all `DebugElements` with a reference to a template local variable named "content":

```
// Filter for DebugElements with a #content reference
const contentRefs = el.queryAll((de) => de.references['content']);
```

The Angular [`By`](/api/platform-browser/By) class has three static methods for common predicates:

| Static method                                                   | Details                                                                    |
| :-------------------------------------------------------------- | :------------------------------------------------------------------------- |
| [`By.all`](/api/platform-browser/By#all)                        | Return all elements                                                        |
| [`By.css(selector)`](/api/platform-browser/By#css)              | Return elements with matching CSS selectors                                |
| [`By.directive(directive)`](/api/platform-browser/By#directive) | Return elements that Angular matched to an instance of the directive class |

```
// Can find DebugElement either by css selector or by directive
const h2 = fixture.debugElement.query(By.css('h2'));
const directive = fixture.debugElement.query(By.directive(Highlight));
```

----
url: https://angular.dev/cli/generate/pipe
----

```
ng generate pipe [name] [options]
```

```
ng generate p [name] [options]
```

Creates a new pipe in your project. Pipes are used to transform data for display in templates. They take input values and apply a specific transformation, such as formatting dates, currency, or filtering arrays. This schematic generates the necessary files and boilerplate code for a new pipe.

## [Arguments](#arguments)

`name`

The name for the new pipe. This will be used to create the pipe's class and spec files (e.g., `my-pipe.pipe.ts` and `my-pipe.pipe.spec.ts`).

Value Type`string`

## [Options](#options)

`export`

Automatically export the pipe from the specified NgModule, making it accessible to other modules in the application.

Value Type`boolean`Default`false`

`flat`

Creates the new pipe files at the top level of the current project. If set to false, a new folder with the pipe's name will be created to contain the files.

Value Type`boolean`Default`true`

`module`

Alias`m`

Specify the NgModule where the pipe should be declared. If not provided, the CLI will attempt to find the closest NgModule in the pipe's path.

Value Type`string`

`project`

The name of the project where the pipe should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`skip-import`

Do not automatically import the new pipe into its closest NgModule.

Value Type`boolean`Default`false`

`skip-tests`

Prevent the generation of a unit test file `spec.ts` for the new pipe.

Value Type`boolean`Default`false`

`standalone`

Generate a standalone pipe. Standalone pipes are self-contained and don't need to be declared in an NgModule. They can be used independently or imported directly into other standalone components, directives, or pipes.

Value Type`boolean`Default`true`

`type-separator`

The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.pipe.ts`.

Value Type`string`Allowed Values`-`, `.`Default`-`

Jump to details

----
url: https://angular.dev/guide/http/http-resource
----

[`httpResource`](/api/common/http/httpResource) is a reactive wrapper around [`HttpClient`](/api/common/http/HttpClient) that gives you the request status and response as signals. You can thus use these signals with [`computed`](/api/core/computed), [`effect`](/api/core/effect), [`linkedSignal`](/api/core/linkedSignal), or any other reactive API. Because it's built on top of [`HttpClient`](/api/common/http/HttpClient), [`httpResource`](/api/common/http/httpResource) supports all the same features, such as interceptors.

For more about Angular's [`resource`](/api/core/resource) pattern, see [Async reactivity with `resource`](/guide/signals/resource).

## [`Using httpResource`](#using-httpresource)

**TIP:** [`httpResource`](/api/common/http/httpResource) uses the globally available [`HttpClient`](/api/common/http/HttpClient). Use `provideHttpClient(...)` only when you need to configure HTTP features, such as interceptors or XSRF options. See [Setting up HttpClient](/guide/http/setup) for details.

You can define an HTTP resource by returning a url:

```
userId = input.required<string>();

user = httpResource(() => `/api/user/${userId()}`); // A reactive function as argument
```

[`httpResource`](/api/common/http/httpResource) is reactive, meaning that whenever one of the signals it depends on changes (like `userId`), the resource will emit a new http request. If a request is already pending, the resource cancels the outstanding request before issuing a new one.

**HELPFUL:** [`httpResource`](/api/common/http/httpResource) differs from the [`HttpClient`](/api/common/http/HttpClient) as it initiates the request *eagerly*. In contrast, the [`HttpClient`](/api/common/http/HttpClient) only initiates requests upon subscription to the returned `Observable`.

For more advanced requests, you can define a request object similar to the request taken by [`HttpClient`](/api/common/http/HttpClient). Each property of the request object that should be reactive should be composed by a signal.

```
user = httpResource(() => ({
  url: `/api/user/${userId()}`,
  method: 'GET',
  headers: {
    'X-Special': 'true',
  },
  params: {
    'fast': 'yes',
  },
  reportProgress: true,
  transferCache: true,
  keepalive: true,
  mode: 'cors',
  redirect: 'error',
  priority: 'high',
  cache: 'force-cache',
  credentials: 'include',
  referrer: 'no-referrer',
  integrity: 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GhEXAMPLEKEY=',
  referrerPolicy: 'no-referrer',
}));
```

**TIP:** Avoid using [`httpResource`](/api/common/http/httpResource) for *mutations* like `POST` or `PUT`. Instead, prefer directly using the underlying [`HttpClient`](/api/common/http/HttpClient) APIs.

The signals of the [`httpResource`](/api/common/http/httpResource) can be used in the template to control which elements should be displayed.

```
@if (user.hasValue()) {
  <user-details [user]="user.value()" />
} @else if (user.error()) {
  <div>Could not load user information</div>
} @else if (user.isLoading()) {
  <div>Loading user info...</div>
}
```

**HELPFUL:** Reading the `value` signal on a [`resource`](/api/core/resource) that is in error state throws at runtime. It is recommended to guard `value` reads with `hasValue()`.

### [Response types](#response-types)

By default, [`httpResource`](/api/common/http/httpResource) returns and parses the response as JSON. However, you can specify an alternate return type with additional functions on [`httpResource`](/api/common/http/httpResource):

```
httpResource.text(() => ({ … })); // returns a string in value()

httpResource.blob(() => ({ … })); // returns a Blob object in value()

httpResource.arrayBuffer(() => ({ … })); // returns an ArrayBuffer in value()
```

### [Tracking download progress](#tracking-download-progress)

Set `reportProgress: true` in the request object to track the download progress of the response. The latest [`HttpProgressEvent`](/api/common/http/HttpProgressEvent) is available through the `progress` signal of the resource:

```
file = httpResource.blob(() => ({
  url: `/api/files/${fileId()}`,
  reportProgress: true,
}));

downloaded = computed(() => this.file.progress()?.loaded ?? 0);
```

**NOTE:** Unlike [`HttpClient`](/api/common/http/HttpClient), where `reportProgress` is deprecated in favor of `reportUploadProgress` and `reportDownloadProgress`, [`httpResource`](/api/common/http/httpResource) keeps the single `reportProgress` option. It only enables download progress events.

## [Response parsing and validation](#response-parsing-and-validation)

When fetching data, you may want to validate responses against a predefined schema, often using popular open-source libraries like [Zod](https://zod.dev) or [Valibot](https://valibot.dev). You can integrate validation libraries like this with [`httpResource`](/api/common/http/httpResource) by specifying a `parse` option. The return type of the `parse` function determines the type of the resource's `value`.

The following example uses Zod to parse and validate the response from the [StarWars API](https://swapi.info/). The resource is then typed the same as the output type of Zod’s parsing.

```
const starWarsPersonSchema = z.object({
  name: z.string(),
  height: z.number({coerce: true}),
  edited: z.string().datetime(),
  films: z.array(z.string()),
});

export class CharacterViewer {
  id = signal(1);

  swPersonResource = httpResource(() => `https://swapi.info/api/people/${this.id()}`, {
    parse: starWarsPersonSchema.parse,
  });
}
```

## [Testing an httpResource](#testing-an-httpresource)

Because [`httpResource`](/api/common/http/httpResource) is a wrapper around [`HttpClient`](/api/common/http/HttpClient), you can test [`httpResource`](/api/common/http/httpResource) with the exact same APIs as [`HttpClient`](/api/common/http/HttpClient). See [HttpClient Testing](/guide/http/testing) for details.

The following example shows a unit test for code using [`httpResource`](/api/common/http/httpResource).

```
TestBed.configureTestingModule({
  providers: [provideHttpClientTesting()],
});

const id = signal(0);
const mockBackend = TestBed.inject(HttpTestingController);
const response = httpResource(() => `/data/${id()}`, {injector: TestBed.inject(Injector)});
TestBed.tick(); // Triggers the effect
const firstRequest = mockBackend.expectOne('/data/0');
firstRequest.flush(0);

// Ensures the values are propagated to the httpResource
await TestBed.inject(ApplicationRef).whenStable();

expect(response.value()).toEqual(0);
```

----
url: https://angular.dev/api/core/runInInjectionContext
----

## [API](/api/core/runInInjectionContext#api)

```
function runInInjectionContext<ReturnT>(
  injector: Injector,
  fn: () => ReturnT,
): ReturnT;
```

@paraminjector`Injector`

the injector which will satisfy calls to [`inject`](api/core/inject) while `fn` is executing

@paramfn`() => ReturnT`

the closure to be run in the context of `injector`

@returns`ReturnT`

the return value of the function, if any

## [Description](/api/core/runInInjectionContext#description)

Runs the given function in the [context](guide/di/dependency-injection-context) of the given [`Injector`](/api/core/Injector).

Within the function's stack frame, [`inject`](api/core/inject) can be used to inject dependencies from the given [`Injector`](/api/core/Injector). Note that [`inject`](/api/core/inject) is only usable synchronously, and cannot be used in any asynchronous callbacks or after any `await` points.

Jump to details

----
url: https://angular.dev/api/core/ModelSignal
----

```
    
      interface ModelSignal<T> extends WritableSignal<T>, InputSignal<T>, OutputRef<T> {}
    
    
```

Directly set the signal to a new value, and notify any dependents.

@paramvalue`T`

@returns`void`

Update the value of the signal based on its current value, and notify any dependents.

@paramupdateFn`(value: T) => T`

@returns`void`

Returns a readonly version of this signal. Readonly signals can be accessed to read their value but can't be changed using set or update methods. The readonly signals do *not* have any built-in mechanism that would prevent deep-mutation of their value.

@returns`Signal<T>`

Registers a callback that is invoked whenever the output emits a new value of type `T`.

Angular will automatically clean up the subscription when the directive/component of the output is destroyed.

@paramcallback`(value: T) => void`

@returns`OutputRefSubscription`

## [Description](#description)

[`ModelSignal`](/api/core/ModelSignal) represents a special [`Signal`](/api/core/Signal) for a directive/component model field.

A model signal is a writeable signal that can be exposed as an output. Whenever its value is updated, it emits to the output.

Jump to details

----
url: https://angular.dev/api/core/CSP_NONCE
----

```
    
      const CSP_NONCE: InjectionToken<string | null>;
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG05201
----

Angular throws this error, with the message `unsafe value used in a resource URL context`, when [`DomSanitizer.sanitize()`](/api/platform-browser/DomSanitizer#sanitize) is called with [`SecurityContext.RESOURCE_URL`](/api/core/SecurityContext#RESOURCE_URL) and a value that hasn't been explicitly marked as trusted.

Resource URLs are treated differently from regular URLs. Angular can sanitize a plain URL, but it can't make an arbitrary resource URL safe because resource URL attributes, like `<iframe src>` or `<link href>`, cause the browser to fetch and execute external content. So `sanitize()` rejects untrusted values rather than trying to sanitize them.

Binding an untrusted value to one of these attributes in a template or host binding throws [NG0904](errors/NG0904) instead.

## [Debugging the error](#debugging-the-error)

Look for a `sanitize()` call that uses [`SecurityContext.RESOURCE_URL`](/api/core/SecurityContext#RESOURCE_URL). The error is thrown for any plain string, even a static-looking URL:

```
@Component({
  selector: 'app-root',
  template: `<iframe [src]="url"></iframe>`,
})
export class App {
  private readonly sanitizer = inject(DomSanitizer);
  url = this.sanitizer.sanitize(SecurityContext.RESOURCE_URL, 'https://example.com/embed'); // throws NG05201
}
```

If the URL comes from a trusted, controlled source, mark it safe using [`DomSanitizer.bypassSecurityTrustResourceUrl`](/api/platform-browser/DomSanitizer#bypassSecurityTrustResourceUrl) and bind the result directly:

```
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  template: `<iframe [src]="safeUrl"></iframe>`,
})
export class App {
  safeUrl: SafeResourceUrl = inject(DomSanitizer).bypassSecurityTrustResourceUrl(
    'https://example.com/embed',
  );
}
```

**CRITICAL:** `bypassSecurityTrustResourceUrl` is an escape hatch, not a sanitizer. Never pass user-supplied URLs through it — that would allow an attacker to load arbitrary content, including malicious scripts. Only use it when the value is fully under your control.

For more information, see the [Security guide](best-practices/security#trusting-safe-values).

----
url: https://angular.dev/api/core/ClassProvider
----

```
    
      interface ClassProvider extends ClassSansProvider {}
    
    
```

An injection token. (Typically an instance of [`Type`](/api/core/Type) or [`InjectionToken`](/api/core/InjectionToken), but can be `any`).

When true, injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

Class to instantiate for the `token`.

## [Usage Notes](#usage-notes)

```
abstract class Shape {
          name!: string;
        }

        class Square extends Shape {
          override name = 'square';
        }

        const injector = Injector.create({providers: [{provide: Shape, useValue: new Square()}]});

        const shape: Shape = injector.get(Shape);
        expect(shape.name).toEqual('square');
        expect(shape instanceof Square).toBe(true);
```

Note that following two providers are not equal:

```
class Greeting {
          salutation = 'Hello';
        }

        class FormalGreeting extends Greeting {
          override salutation = 'Greetings';
        }

        const injector = Injector.create({
          providers: [
            {provide: FormalGreeting, useClass: FormalGreeting},
            {provide: Greeting, useClass: FormalGreeting},
          ],
        });

        // The injector returns different instances.
        // See: {provide: ?, useExisting: ?} if you want the same instance.
        expect(injector.get(FormalGreeting)).not.toBe(injector.get(Greeting));
```

### [Multi-value example](#multi-value-example)

```
const locale = new InjectionToken<string[]>('locale');
        const injector = Injector.create({
          providers: [
            {provide: locale, multi: true, useValue: 'en'},
            {provide: locale, multi: true, useValue: 'sk'},
          ],
        });

        const locales: string[] = injector.get(locale);
        expect(locales).toEqual(['en', 'sk']);
```

Jump to details

----
url: https://angular.dev/api/forms/RadioControlValueAccessor
----

```
    
      class RadioControlValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor ,OnDestroy ,OnInit {}
    
    
```

@paramrenderer`Renderer2`

@paramelementRef`ElementRef<any>`

@param\_registry`RadioControlRegistry`

@param\_injector`Injector`

@returns`RadioControlValueAccessor`

Tracks the name of the radio input element.

Tracks the name of the [`FormControl`](/api/forms/FormControl) bound to the directive. The name corresponds to a key in the parent [`FormGroup`](/api/forms/FormGroup) or [`FormArray`](/api/forms/FormArray).

Tracks the value of the radio input element

Sets the "value" on the radio input element and unchecks it.

@paramvalue`any`

@returns`void`

## [Description](#description)

The [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing radio control values and listening to radio control changes. The value accessor is used by the [`FormControlDirective`](/api/forms/FormControlDirective), [`FormControlName`](/api/forms/FormControlName), and [`NgModel`](/api/forms/NgModel) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using radio buttons with reactive form directives](#using-radio-buttons-with-reactive-form-directives)

The follow example shows how to use radio buttons in a reactive form. When using radio buttons in a reactive form, radio buttons in the same group should have the same `formControlName`. Providing a `name` attribute is optional.

```
import {Component} from '@angular/core';
import {FormControl, FormGroup} from '@angular/forms';

@Component({
  selector: 'example-app',
  template: `
    <form [formGroup]="form">
      <input type="radio" formControlName="food" value="beef" /> Beef
      <input type="radio" formControlName="food" value="lamb" /> Lamb
      <input type="radio" formControlName="food" value="fish" /> Fish
    </form>

    <p>Form value: {{ form.value | json }}</p>
    <!-- {food: 'lamb' } -->
  `,
  standalone: false,
})
export class ReactiveRadioButtonComp {
  form = new FormGroup({
    food: new FormControl('lamb'),
  });
}
```

Jump to details

----
url: https://angular.dev/api/forms/signals/FieldValidator
----

```
    
      type FieldValidator<TValue, TPathKind extends PathKind = PathKind.Root> = LogicFn<  TValue,  ValidationResult<ValidationError.WithoutFieldTree>,  TPathKind>
    
    
```

Jump to details

----
url: https://angular.dev/api/router/isActive
----

```
function isActive(
  url: string | UrlTree,
  router: Router,
  matchOptions?: Partial<IsActiveMatchOptions> | undefined,
): Signal<boolean>;
```

@paramurl`string | UrlTree`

@paramrouter`Router`

@parammatchOptions`Partial<IsActiveMatchOptions> | undefined`

@returns`Signal<boolean>`

## [Description](#description)

Returns a computed signal of whether the given url is activated in the Router.

As the router state changes, the signal will update to reflect whether the url is active.

When using the `matchOptions` argument, any missing properties fall back to the following defaults:

* `paths`: 'subset'
* `queryParams`: 'subset'
* `matrixParams`: 'ignored'
* `fragment`: 'ignored'

Jump to details

----
url: https://angular.dev/api/common/provideImgixLoader
----

```
    
      const provideImgixLoader: (path: string) => Provider[];
    
    
```

Jump to details

----
url: https://angular.dev/api/ssr/IS_DISCOVERING_ROUTES
----

```
    
      const IS_DISCOVERING_ROUTES: InjectionToken<boolean>;
    
    
```

## [Description](#description)

A DI token that indicates whether the application is in the process of discovering routes.

This token is provided with the value `true` when route discovery is active, allowing other parts of the application to conditionally execute logic. For example, it can be used to disable features or behaviors that are not necessary or might interfere with the route discovery process.

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/19-creating-an-injectable-service
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/18-forms-validation)[](/tutorials/learn-angular/20-inject-based-di)

[](/tutorials/learn-angular/18-forms-validation)[](/tutorials/learn-angular/20-inject-based-di)

Dependency injection (DI) in Angular is one of the framework's most powerful features. Consider dependency injection to be the ability for Angular to *provide* resources you need for your application at runtime. A dependency could be a service or some other resources.

**NOTE:** Learn more about [dependency injection in the essentials guide](/essentials/dependency-injection).

In this activity, you'll learn how to create an `injectable` service.

***

One way to use a service is to act as a way to interact with data and APIs. To make a service reusable you should keep the logic in the service and share it throughout the application when it is needed.

To make a class eligible to be injected by the DI system, use the [`@Service`](/api/core/Service) decorator. For example:

```
@Service()
class UserService {
  // methods to retrieve and return data
}
```

The [`@Service`](/api/core/Service) decorator marks the class as a service and notifies the DI system that `UserService` can be accessed anywhere in your application. By default, Angular provides the service across your entire application, so you don't need to write any extra configuration.

**NOTE:** By default, [`@Service`](/api/core/Service) provides the class at the root injector. If you want to provide it manually, for example, to scope it to a specific route or component, set `autoProvided: false`. Learn more in the [guide on creating and using services](guide/di/creating-and-using-services#using-the-service-vs-injectable-decorator).

Alright, you try:

1. ### [Add the `@Service` decorator](#add-the-service-decorator)

   Update the code in `car.service.ts` by adding the [`@Service()`](/api/core/Service) decorator to the `CarService` class.

   **TIP:** Use the above example to find the correct syntax.

Well, done 👍 that service is now `injectable` and can participate in the fun. Now that the service is `injectable`, let's try injecting it into a component 👉

----
url: https://angular.dev/api/cdk/testing/HarnessQuery
----

```
    
      type HarnessQuery<T extends ComponentHarness> = | ComponentHarnessConstructor<T>  | HarnessPredicate<T>
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpClientModule
----

use `provideHttpClient(withInterceptorsFromDi())` as providers instead

## [API](#api)

```
    
      class HttpClientModule {}
    
    
```

## [Description](#description)

Configures the dependency injector for [`HttpClient`](/api/common/http/HttpClient) with supporting services for XSRF. Automatically imported by [`HttpClientModule`](/api/common/http/HttpClientModule).

You can add interceptors to the chain behind [`HttpClient`](/api/common/http/HttpClient) by binding them to the multiprovider for built-in DI token [`HTTP_INTERCEPTORS`](/api/common/http/HTTP_INTERCEPTORS).

When importing the [`HttpClientModule`](/api/common/http/HttpClientModule), the [`HttpBackend`](/api/common/http/HttpBackend) is set to using the [`HttpXhrBackend`](/api/common/http/HttpXhrBackend). If you want to use the [`FetchBackend`](/api/common/http/FetchBackend), use [`provideHttpClient`](/api/common/http/provideHttpClient) instead.

Jump to details

----
url: https://angular.dev/api/core/AfterViewInit
----

```
    
      interface AfterViewInit {}
    
    
```

A callback method that is invoked immediately after Angular has completed initialization of a component's view. It is invoked only once when the view is instantiated.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define its own view initialization method.

```
@Component({
        selector: 'my-cmp',
        template: `...`,
      })
      class MyComponent implements AfterViewInit {
        ngAfterViewInit() {
          // ...
        }
      }
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8112
----

This diagnostic detects `@let` declarations whose value is never read in the template.

```
import {Component} from '@angular/core';

@Component({
  template: `
    @let fullName = user.firstName + ' ' + user.lastName;
    <h1>Welcome</h1>
  `,
})
class MyComponent {
  user = {firstName: 'Ada', lastName: 'Lovelace'};
}
```

## [What's wrong with that?](#whats-wrong-with-that)

An unused `@let` is usually left over from a refactor, or points to a place where the value was meant to be used. Angular still evaluates the expression of an unused `@let` each time the template updates. For example, a function call in the expression still runs.

## [What should I do instead?](#what-should-i-do-instead)

Either use the declaration or remove it:

Prefer

### use the declaration

```
@Component({
  template: `
    @let fullName = user.firstName + ' ' + user.lastName;
    <h1>Welcome, {{ fullName }}</h1>
  `,
})
class MyComponent {
  user = {firstName: 'Ada', lastName: 'Lovelace'};
}
```

Prefer

### remove the declaration

```
@Component({
  template: `<h1>Welcome</h1>`,
})
class MyComponent {}
```

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "unusedLetDeclaration": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/guide/forms/signals/overview
----

# Forms with Angular Signals

[](https://github.com/angular/angular/edit/main/adev/src/content/guide/forms/signals/overview.md "Edit this page")

Signal Forms is a library that allows you to manage form state in Angular applications by building on the reactive foundation of signals. With automatic two-way binding, type-safe field access, and schema-based validation, Signal Forms help you create robust forms.

**TIP:** For a quick introduction to Signal Forms, see the [Signal Forms essentials guide](essentials/signal-forms).

## [Why Signal Forms?](#why-signal-forms)

Building forms in web applications involves managing several interconnected concerns: tracking field values, validating user input, handling error states, and keeping the UI synchronized with your data model. Managing these concerns separately creates boilerplate code and complexity.

Signal Forms address these challenges by:

* **Synchronizing state automatically** - Automatically syncs the form data model with bound form fields
* **Providing type safety** - Supports fully type safe schemas & bindings between your UI controls and data model
* **Centralizing validation logic** - Define all validation rules in one place using a validation schema

Signal Forms work best in new applications built with signals. If you're working with an existing application that uses reactive forms, reactive forms remain a solid choice.

**NOTE:** If you're coming from template or reactive forms, you may be interested in the [comparison guide](guide/forms/signals/comparison).

## [Prerequisites](#prerequisites)

Signal Forms require:

* Angular v21 or higher

## [Setup](#setup)

Signal Forms are already included in the `@angular/forms` package. Import the necessary functions and directives from `@angular/forms/signals`:

```
import {form, FormField, required, email} from '@angular/forms/signals';
```

The [`FormField`](/api/forms/signals/FormField) directive must be imported into any component that binds form fields to HTML inputs:

```
@Component({
  // ...
  imports: [FormField],
})
```

## [Next steps](#next-steps)

To learn more about how Signal Forms work, check out the following guides:

----
url: https://angular.dev/guide/di/di-in-action
----

This guide explores additional features of dependency injection (DI) in Angular.

**NOTE:** For comprehensive coverage of InjectionToken and custom providers, see the [defining dependency providers guide](guide/di/defining-dependency-providers#injection-tokens).

## [Inject the component's DOM element](#inject-the-components-dom-element)

Although developers generally avoid it, some visual effects and third-party tools require you to access the DOM directly. In such cases, you may need to access a component's DOM element.

Angular exposes the underlying DOM element of a [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) through injection using the [`ElementRef`](/api/core/ElementRef) token:

```
import {Directive, ElementRef, inject} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
})
export class HighlightDirective {
  private element = inject(ElementRef);

  update() {
    this.element.nativeElement.style.color = 'red';
  }
}
```

## [Inject the host element's tag name](#inject-the-host-elements-tag-name)

To get the tag name of a host element, inject it using the [`HOST_TAG_NAME`](/api/core/HOST_TAG_NAME) token.

```
import {Directive, HOST_TAG_NAME, inject} from '@angular/core';

@Directive({
  selector: '[roleButton]',
})
export class RoleButtonDirective {
  private tagName = inject(HOST_TAG_NAME);

  onAction() {
    switch (this.tagName) {
      case 'button':
        // Handle button action
        break;
      case 'a':
        // Handle anchor action
        break;
      default:
        // Handle other elements
        break;
    }
  }
}
```

**NOTE:** If the host element might not have a tag name (e.g., [`ng-container`](/api/core/ng-container) or [`ng-template`](/api/core/ng-template)), make the injection optional.

## [Resolve circular dependencies with a forward reference](#resolve-circular-dependencies-with-a-forward-reference)

In TypeScript, the order of class declarations matters. You cannot reference a class directly until you define it.

This isn't usually a problem, especially if you adhere to the recommended *one class per file* rule. However, in some cases, circular references are unavoidable. For example, if class 'A' refers to class 'B' and class 'B' refers to class 'A', one of them must be defined first.

The Angular [`forwardRef()`](/api/core/forwardRef) function creates an *indirect* reference that Angular can resolve later.

You face a similar problem when a class makes *a reference to itself*. For example, in its `providers` array. The `providers` array is a property of the [`@Component()`](/api/core/Component) decorator function, which must appear before the class definition. Such circular references can be resolved using [`forwardRef`](/api/core/forwardRef).

### app.component.ts

```
providers: [
  {
    provide: PARENT_MENU_ITEM,
    useExisting: forwardRef(() => MenuItem),
  },
],
```

----
url: https://angular.dev/api/router/ComponentInputBindingFeature
----

```
    
      type ComponentInputBindingFeature = RouterFeature<RouterFeatureKind.ComponentInputBindingFeature>
    
    
```

Jump to details

----
url: https://angular.dev/api/router/nonBlocking
----

```
function nonBlocking<T, R extends Resource<T>>(res: R): R;
```

@paramres`R`

@returns`R`

Jump to details

----
url: https://angular.dev/api/forms/signals/SchemaPathTree
----

```
    
      type SchemaPathTree<TModel, TPathKind extends PathKind = PathKind.Root> = ([TModel] extends [AbstractControl]    ? CompatSchemaPath<TModel, TPathKind>    : SchemaPath<TModel, SchemaPathRules.Supported, TPathKind>) &    // Subpaths    ([TModel] extends [AbstractControl]      ? unknown      : // Array paths have no subpaths        [TModel] extends [ReadonlyArray<any>]        ? unknown        : // Object subfields          TModel extends Record<string, any>          ? {[K in keyof TModel]: MaybeSchemaPathTree<TModel[K], PathKind.Child>}          : // Primitive or other type - no subpaths            unknown)
    
    
```

## [Description](#description)

Nested schema path.

It mirrors the structure of a given data structure, and allows applying rules to the appropriate fields.

Jump to details

----
url: https://angular.dev/api/forms/NgControl
----

```
    
      abstract class NgControl extends AbstractControlDirective {}
    
    
```

@paraminjector`Injector | undefined`

@paramrenderer`Renderer2 | undefined`

@paramrawValueAccessors`ControlValueAccessor[] | undefined`

@returns`NgControl`

The name for the control

The value accessor for the control

The callback method to update the model from the view when requested

@paramnewValue`any`

The new value for the view

@returns`void`

Validator function that returns current parse errors.

Tracks last bound values to avoid unnecessary FVC updates.

@returns`void`

@paramhost`ControlDirectiveHost<undefined>`

@parambindRequired`boolean`

@returns`void`

Whether the control should bind the `required` property (in custom control mode).

Can be overridden by subclasses that handle `required` in a different way.

@paramcontrol`AbstractControl<any, any, any> | null | undefined`

@returns`void`

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

The path to the 'street' control from the root form would be 'address' -> 'street'.

It can be provided to this method in one of two formats:

1. An array of string control names, e.g. `['address', 'street']`
2. A period-delimited list of control names in one string, e.g. `'address.street'`

Jump to details

----
url: https://angular.dev/api/router/MaybeAsync
----

```
    
      type MaybeAsync<T> = T | Observable<T> | Promise<T>
    
    
```

Jump to details

----
url: https://angular.dev/ecosystem/rxjs-interop/take-until-destroyed
----

**TIP:** This guide assumes you're familiar with [component and directive lifecycle](guide/components/lifecycle).

The [`takeUntilDestroyed`](/api/core/rxjs-interop/takeUntilDestroyed) operator, from `@angular/core/rxjs-interop`, provides a concise and reliable way to automatically unsubscribe from an Observable when a component or directive is destroyed. This prevents common memory leaks with RxJS subscriptions. It works similarly to the RxJS [`takeUntil`](https://rxjs.dev/api/operators/takeUntil) operator but without the need for a separate Subject.

```
import {Component, inject} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {NotificationDispatcher, CustomPopupShower} from './some-shared-project-code';

@Component(/* ... */)
export class UserProfile {
  private dispatcher = inject(NotificationDispatcher);
  private popup = inject(CustomPopupShower);

  constructor() {
    // This subscription the 'notifications' Observable is automatically
    // unsubscribed when the 'UserProfile' component is destroyed.
    const messages: Observable<string> = this.dispatcher.notifications;
    messages.pipe(takeUntilDestroyed()).subscribe((message) => {
      this.popup.show(message);
    });
  }
}
```

The [`takeUntilDestroyed`](/api/core/rxjs-interop/takeUntilDestroyed) operator accepts a single optional [`DestroyRef`](/api/core/DestroyRef) argument. The operator uses [`DestroyRef`](/api/core/DestroyRef) to know when the component or directive has been destroyed. You can omit this argument when calling [`takeUntilDestroyed`](/api/core/rxjs-interop/takeUntilDestroyed) in an [injection context](/guide/di/dependency-injection-context), typically the constructor of a component or directive. Always provide a [`DestroyRef`](/api/core/DestroyRef) if your code may call [`takeUntilDestroyed`](/api/core/rxjs-interop/takeUntilDestroyed) outside of an injection context.

```
@Component(/* ... */)
export class UserProfile {
  private dispatcher = inject(NotificationDispatcher);
  private popup = inject(CustomPopupShower);
  private destroyRef = inject(DestroyRef);

  startListeningToNotifications() {
    // Always pass a `DestroyRef` if you call `takeUntilDestroyed` outside
    // of an injection context.
    const messages: Observable<string> = this.dispatcher.notifications;
    messages.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((message) => {
      this.popup.show(message);
    });
  }
}
```

----
url: https://angular.dev/api/core/ResourceParamsStatus
----

```
    
      class ResourceParamsStatus extends Error {}
    
    
```

@parammsg`string`

@returns`ResourceParamsStatus`

Stack trace where extra frames have been removed and zone names added.

Original stack trace with no modifications

Status code that transitions the resource to `idle` status.

Status code that transitions the resource to `loading` status.

Jump to details

----
url: https://angular.dev/api/core/ValueSansProvider
----

```
    
      interface ValueSansProvider {}
    
    
```

The value to inject.

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/CdkDropListGroup
----

```
    
      class CdkDropListGroup<T> implements OnDestroy {}
    
    
```

Whether starting a dragging sequence from inside this group is disabled.

@returns`void`

Jump to details

----
url: https://angular.dev/api/common/WeekDay
----

Week locale getters are deprecated

## [API](#api)

```
    
      enum WeekDay {}
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ResourceStreamingLoader
----

```
    
      type ResourceStreamingLoader<T, R> = (  param: ResourceLoaderParams<R>,) => Signal<ResourceStreamItem<T>> | PromiseLike<Signal<ResourceStreamItem<T>>> | undefined
    
    
```

Jump to details

----
url: https://angular.dev/best-practices/slow-computations
----

On every change detection cycle, Angular synchronously:

* Evaluates all template expressions in all components, unless specified otherwise, based on that each component's detection strategy
* Executes the `ngDoCheck`, `ngAfterContentChecked`, `ngAfterViewChecked`, and `ngOnChanges` lifecycle hooks. A single slow computation within a template or a lifecycle hook can slow down the entire change detection process because Angular runs the computations sequentially.

## [Identifying slow computations](#identifying-slow-computations)

You can identify heavy computations with Angular DevTools’ profiler. In the performance timeline, click a bar to preview a particular change detection cycle. This displays a bar chart, which shows how long the framework spent in change detection for each component. When you click a component, you can preview how long Angular spent evaluating its template and lifecycle hooks.

For example, in the preceding screenshot, the second recorded change detection cycle is selected. Angular spent over 573 ms on this cycle, with the most time spent in the `EmployeeListComponent`. In the details panel, you can see that Angular spent over 297 ms evaluating the template of the `EmployeeListComponent`.

## [Optimizing slow computations](#optimizing-slow-computations)

Here are several techniques to remove slow computations:

* **Optimizing the underlying algorithm**. This is the recommended approach. If you can speed up the algorithm that is causing the problem, you can speed up the entire change detection mechanism.
* **Caching using pure pipes**. You can move the heavy computation to a pure [pipe](guide/templates/pipes). Angular reevaluates a pure pipe only if it detects that its inputs have changed, compared to the previous time Angular called it.
* **Using memoization**. [Memoization](https://en.wikipedia.org/wiki/Memoization) is a similar technique to pure pipes, with the difference that pure pipes preserve only the last result from the computation where memoization could store multiple results.
* **Avoid repaints/reflows in lifecycle hooks**. Certain [operations](https://web.dev/avoid-large-complex-layouts-and-layout-thrashing/) cause the browser to either synchronously recalculate the layout of the page or re-render it. Since reflows and repaints are generally slow, you want to avoid performing them in every change detection cycle.

Pure pipes and memoization have different trade-offs. Pure pipes are an Angular built-in concept compared to memoization, which is a general software engineering practice for caching function results. The memory overhead of memoization could be significant if you invoke the heavy computation frequently with different arguments.

----
url: https://angular.dev/cli/generate/component
----

```
ng generate component [name] [options]
```

```
ng generate c [name] [options]
```

Creates a new Angular component. Components are the basic building blocks of Angular applications. Each component consists of a TypeScript class, an HTML template, and an optional CSS stylesheet. Use this schematic to generate a new component in your project.

## [Arguments](#arguments)

`name`

The name for the new component. This will be used to create the component's class, template, and stylesheet files. For example, if you provide `my-component`, the files will be named `my-component.ts`, `my-component.html`, and `my-component.css`.

Value Type`string`

## [Options](#options)

`add-type-to-class-name`

When true, the 'type' option will be appended to the generated class name. When false, only the file name will include the type.

Value Type`boolean`Default`true`

`change-detection`

Alias`c`

Configures the change detection strategy for the component.

Value Type`string`Allowed Values`Eager`, `OnPush`Default`OnPush`

`display-block`

Alias`b`

Adds `:host { display: block; }` to the component's stylesheet, ensuring the component renders as a block-level element. This is useful for layout purposes.

Value Type`boolean`Default`false`

`export`

Automatically export the component from the specified NgModule, making it accessible to other modules in the application.

Value Type`boolean`Default`false`

`export-default`

Use a default export for the component in its TypeScript file instead of a named export.

Value Type`boolean`Default`false`

`flat`

Create the component files directly in the project's `src/app` directory instead of creating a new folder for them.

Value Type`boolean`Default`false`

`inline-style`

Alias`s`

Include the component's styles directly in the `component.ts` file. By default, a separate stylesheet file (e.g., `my-component.css`) is created.

Value Type`boolean`Default`false`

`inline-template`

Alias`t`

Include the component's HTML template directly in the `component.ts` file. By default, a separate template file (e.g., `my-component.html`) is created.

Value Type`boolean`Default`false`

`module`

Alias`m`

Specify the NgModule where the component should be declared. If not provided, the CLI will attempt to find the closest NgModule in the component's path.

Value Type`string`

`ng-html`

Generate component template files with an '.ng.html' file extension instead of '.html'.

Value Type`boolean`Default`false`

`prefix`

Alias`p`

A prefix to be added to the component's selector. For example, if the prefix is `app` and the component name is `my-component`, the selector will be `app-my-component`.

Value Type`string`

`project`

The name of the project where the component should be added. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`selector`

The HTML selector to use for this component. If not provided, a selector will be generated based on the component name (e.g., `app-my-component`).

Value Type`string`

`skip-import`

Do not automatically import the new component into its closest NgModule.

Value Type`boolean`Default`false`

`skip-selector`

Skip the generation of an HTML selector for the component.

Value Type`boolean`Default`false`

`skip-tests`

Skip the generation of unit test files `spec.ts`.

Value Type`boolean`Default`false`

`standalone`

Generate a standalone component. Standalone components are self-contained and don't need to be declared in an NgModule. They can be used independently or imported directly into other standalone components.

Value Type`boolean`Default`true`

`style`

Specify the type of stylesheet to be created for the component, or `none` to skip creating a stylesheet.

Value Type`string`Allowed Values`css`, `less`, `none`, `sass`, `scss`Default`css`

`type`

Append a custom type to the component's filename. For example, if you set the type to `container`, the file will be named `my-component.container.ts`.

Value Type`string`

`view-encapsulation`

Alias`v`

Sets the view encapsulation mode for the component. This determines how the component's styles are scoped and applied.

Value Type`string`Allowed Values`Emulated`, `None`, `ShadowDom`

Jump to details

----
url: https://angular.dev/guide/di/debugging-and-troubleshooting-di
----

Dependency injection (DI) issues typically stem from configuration mistakes, scope problems, or incorrect usage patterns. This guide helps you identify and resolve common DI problems that developers encounter.

## [Common pitfalls and solutions](#common-pitfalls-and-solutions)

### [Services not available where expected](#services-not-available-where-expected)

One of the most common DI issues occurs when you try to inject a service but Angular cannot find it in the current injector or any parent injector. This usually happens when the service is provided in the wrong scope or not provided at all.

#### [Provider scope mismatch](#provider-scope-mismatch)

When you provide a service in a component's `providers` array, Angular creates an instance in that component's injector. This instance is only available to that component and its children. Parent components and sibling components cannot access it because they use different injectors.

### child-view\.ts

```
import {Component} from '@angular/core';
import {DataStore} from './data-store';

@Component({
  selector: 'app-child',
  template: '<p>Child</p>',
  providers: [DataStore], // Only available in this component and its children
})
export class ChildView {}
```

### parent-view\.ts

```
import {Component, inject} from '@angular/core';
import {DataStore} from './data-store';

@Component({
  selector: 'app-parent',
  template: '<app-child />',
})
export class ParentView {
  private dataService = inject(DataStore); // ERROR: Not available to parent
}
```

Angular only searches up the hierarchy, never down. Parent components cannot access services provided in child components.

**Solution:** Provide the service at a higher level (application or parent component).

Prefer

```
import {Service} from '@angular/core';

@Service()
export class DataStore {
  // Available everywhere
}
```

**TIP:** [`@Service`](/api/core/Service) makes services available everywhere and enables tree-shaking. If you don't want to scope it to the entire app, specify `autoProvided: false`.

#### [Services and lazy-loaded routes](#services-and-lazy-loaded-routes)

When you provide a service in a lazy-loaded route's `providers` array, Angular creates a child injector for that route. This injector and its services only become available after the route loads. Components in the eagerly-loaded parts of your application cannot access these services because they use different injectors that exist before the lazy-loaded injector is created.

### feature.routes.ts

```
import {Routes} from '@angular/router';
import {FeatureClient} from './feature-client';

export const featureRoutes: Routes = [
  {
    path: 'feature',
    providers: [FeatureClient],
    loadComponent: () => import('./feature-view'),
  },
];
```

### eager-view\.ts

```
import {Component, inject} from '@angular/core';
import {FeatureClient} from './feature-client';

@Component({
  selector: 'app-eager',
  template: '<p>Eager Component</p>',
})
export class EagerView {
  private featureService = inject(FeatureClient); // ERROR: Not available yet
}
```

Lazy-loaded routes create child injectors that are only available after the route loads.

**NOTE:** By default, route injectors and their services persist even after navigating away from the route. They are not destroyed until the application is closed. For automatic cleanup of unused route injectors, see [customizing route behavior](guide/routing/customizing-route-behavior#automatic-cleanup-of-unused-route-injectors).

**Solution:** Use [`@Service`](/api/core/Service) for services that need to be shared across lazy boundaries.

Prefer

### Provide at root for shared services

```
import {Service} from '@angular/core';

@Service()
export class FeatureClient {
  // Available everywhere, including before lazy load
}
```

If the service should be lazy-loaded but still available to eager components, inject it only where needed and use optional injection to handle availability.

### [Multiple instances instead of singletons](#multiple-instances-instead-of-singletons)

You expect one shared instance (singleton) but get separate instances in different components.

#### [Providing in component instead of root](#providing-in-component-instead-of-root)

When you add a service to a component's `providers` array, Angular creates a new instance of that service for each instance of the component. Each component gets its own separate service instance, which means changes in one component don't affect the service instance in other components. This is often unexpected when you want shared state across your application.

Avoid

### Component-level provider creates multiple instances

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-profile',
  template: '<p>Profile</p>',
  providers: [UserClient], // Creates new instance per component!
})
export class UserProfile {
  private userService = inject(UserClient);
}

@Component({
  selector: 'app-settings',
  template: '<p>Settings</p>',
  providers: [UserClient], // Different instance!
})
export class UserSettings {
  private userService = inject(UserClient);
}
```

Each component gets its own `UserClient` instance. Changes in one component don't affect the other.

**Solution:** Use [`@Service`](/api/core/Service) for singletons.

Prefer

### Root-level singleton

```
import {Service} from '@angular/core';

@Service()
export class UserClient {
  // Single instance shared across all components
}
```

#### [When multiple instances are intentional](#when-multiple-instances-are-intentional)

Sometimes you want separate instances per component for component-specific state.

### Intentional: Component-scoped state

```
import {Injectable, signal} from '@angular/core';

@Injectable() // No providedIn - must be provided explicitly
export class FormStateStore {
  private formData = signal({});

  setData(data: any) {
    this.formData.set(data);
  }

  getData() {
    return this.formData();
  }
}

@Component({
  selector: 'app-user-form',
  template: '<form>...</form>',
  providers: [FormStateStore], // Each form gets its own state
})
export class UserForm {
  private formState = inject(FormStateStore);
}
```

This pattern is useful for:

* Form state management (each form has isolated state)
* Component-specific caching
* Temporary data that shouldn't be shared

### [Incorrect inject() usage](#incorrect-inject-usage)

The `inject()` function only works in specific contexts during class construction and factory execution.

#### [Using inject() in lifecycle hooks](#using-inject-in-lifecycle-hooks)

When you call the `inject()` function inside lifecycle hooks like `ngOnInit()`, `ngAfterViewInit()`, or `ngOnDestroy()`, Angular throws an error because these methods run outside the injection context. The injection context is only available during the synchronous execution of class construction, which happens before lifecycle hooks are called.

Avoid

### inject() in ngOnInit

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-profile',
  template: '<p>User: {{userName}}</p>',
})
export class UserProfile {
  userName = '';

  ngOnInit() {
    const userService = inject(UserClient); // ERROR: Not an injection context
    this.userName = userService.getUser().name;
  }
}
```

**Solution:** Capture dependencies and derive values in field initializers.

Prefer

### Derive values in field initializers

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-profile',
  template: '<p>User: {{userName}}</p>',
})
export class UserProfile {
  private userService = inject(UserClient);
  userName = this.userService.getUser().name;
}
```

#### [Using the Injector for deferred injection](#using-the-injector-for-deferred-injection)

When you need to retrieve services outside an injection context, use the captured [`Injector`](/api/core/Injector) directly with `injector.get()`:

```
import {Component, inject, Injector} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-profile',
  template: '<button (click)="delayedLoad()">Load Later</button>',
})
export class UserProfile {
  private injector = inject(Injector);

  delayedLoad() {
    setTimeout(() => {
      const userService = this.injector.get(UserClient);
      console.log(userService.getUser());
    }, 1000);
  }
}
```

#### [Using runInInjectionContext for callbacks](#using-runininjectioncontext-for-callbacks)

Use [`runInInjectionContext()`](/api/core/runInInjectionContext) when you need to enable **other code** to call `inject()`. This is useful when accepting callbacks that might use dependency injection:

```
import {Component, inject, Injector, input, runInInjectionContext} from '@angular/core';

@Component({
  selector: 'app-data-loader',
  template: '<button (click)="load()">Load</button>',
})
export class DataLoader {
  private injector = inject(Injector);
  onLoad = input<() => void>();

  load() {
    const callback = this.onLoad();
    if (callback) {
      // Enable the callback to use inject()
      runInInjectionContext(this.injector, callback);
    }
  }
}
```

The [`runInInjectionContext()`](/api/core/runInInjectionContext) function creates a temporary injection context, allowing code inside the callback to call `inject()`.

**IMPORTANT:** Always capture dependencies at the class level when possible. Use `injector.get()` for simple deferred retrieval, and [`runInInjectionContext()`](/api/core/runInInjectionContext) only when external code needs to call `inject()`.

**TIP:** Use [`assertInInjectionContext()`](/api/core/assertInInjectionContext) to verify your code is running in a valid injection context. This is useful when creating reusable functions that call `inject()`. See [Asserting the context](guide/di/dependency-injection-context#asserts-the-context) for details.

### [providers vs viewProviders confusion](#providers-vs-viewproviders-confusion)

The difference between `providers` and `viewProviders` affects content projection scenarios.

#### [Understanding the difference](#understanding-the-difference)

**providers:** Available to the component's template AND any content projected into the component (ng-content).

**viewProviders:** Only available to the component's template, NOT to projected content.

### parent-view\.ts

```
import {Component, inject} from '@angular/core';
import {ThemeStore} from './theme-store';

@Component({
  selector: 'app-parent',
  template: `
    <div>
      <p>Theme: {{ themeService.theme() }}</p>
      <ng-content />
    </div>
  `,
  providers: [ThemeStore], // Available to content children
})
export class ParentView {
  protected themeService = inject(ThemeStore);
}

@Component({
  selector: 'app-parent-view',
  template: `
    <div>
      <p>Theme: {{ themeService.theme() }}</p>
      <ng-content />
    </div>
  `,
  viewProviders: [ThemeStore], // NOT available to content children
})
export class ParentViewOnly {
  protected themeService = inject(ThemeStore);
}
```

### child-view\.ts

```
import {Component, inject} from '@angular/core';
import {ThemeStore} from './theme-store';

@Component({
  selector: 'app-child',
  template: '<p>Child theme: {{theme()}}</p>',
})
export class ChildView {
  private themeService = inject(ThemeStore, {optional: true});
  theme = () => this.themeService?.theme() ?? 'none';
}
```

### app.ts

```
@Component({
  selector: 'app-root',
  template: `
    <app-parent>
      <app-child />
      <!-- Can access ThemeStore -->
    </app-parent>

    <app-parent-view>
      <app-child />
      <!-- Cannot access ThemeStore -->
    </app-parent-view>
  `,
})
export class App {}
```

**When projected into `app-parent`:** The child component can inject `ThemeStore` because `providers` makes it available to projected content.

**When projected into `app-parent-view`:** The child component cannot inject `ThemeStore` because `viewProviders` restricts it to the parent's template only.

#### [Choosing between providers and viewProviders](#choosing-between-providers-and-viewproviders)

Use `providers` when:

* The service should be available to projected content
* You want content children to access the service
* You're providing general-purpose services

Use `viewProviders` when:

* The service should only be available to your component's template
* You want to hide implementation details from projected content
* You're providing internal services that shouldn't leak out

**Default recommendation:** Use `providers` unless you have a specific reason to restrict access with `viewProviders`.

### [InjectionToken issues](#injectiontoken-issues)

When using [`InjectionToken`](/api/core/InjectionToken) for non-class dependencies, developers often encounter problems related to token identity, type safety, and provider configuration. These issues usually stem from how JavaScript handles object identity and how TypeScript infers types.

#### [Token identity confusion](#token-identity-confusion)

When you create a new [`InjectionToken`](/api/core/InjectionToken) instance, JavaScript creates a unique object in memory. Even if you create another [`InjectionToken`](/api/core/InjectionToken) with the exact same description string, it's a completely different object. Angular uses the token object's identity (not its description) to match providers with injection points, so tokens with the same description but different object identities cannot access each other's values.

### config.token.ts

```
import {InjectionToken} from '@angular/core';

export interface AppConfig {
  apiUrl: string;
}

export const APP_CONFIG = new InjectionToken<AppConfig>('app config');
```

### app.config.ts

```
import {APP_CONFIG} from './config.token';

export const appConfig: AppConfig = {
  apiUrl: 'https://api.example.com',
};

bootstrapApplication(App, {
  providers: [{provide: APP_CONFIG, useValue: appConfig}],
});
```

Avoid

### feature-view\.ts

```
// Creating new token with same description
import {InjectionToken, inject} from '@angular/core';
import {AppConfig} from './config.token';

const APP_CONFIG = new InjectionToken<AppConfig>('app config');

@Component({
  selector: 'app-feature',
  template: '<p>Feature</p>',
})
export class FeatureView {
  private config = inject(APP_CONFIG); // ERROR: Different token instance!
}
```

Even though both tokens have the description `'app config'`, they are different objects. Angular compares tokens by reference, not by description.

**Solution:** Import the same token instance.

Prefer

### feature-view\.ts

```
import {inject} from '@angular/core';
import {APP_CONFIG, AppConfig} from './config.token';

@Component({
  selector: 'app-feature',
  template: '<p>API: {{config.apiUrl}}</p>',
})
export class FeatureView {
  protected config = inject(APP_CONFIG); // Works: Same token instance
}
```

**TIP:** Always export tokens from a shared file and import them everywhere they're needed. Never create multiple [`InjectionToken`](/api/core/InjectionToken) instances with the same description.

#### [Trying to inject interfaces](#trying-to-inject-interfaces)

When you define a TypeScript interface, it only exists during compilation for type checking. TypeScript erases all interface definitions when it compiles to JavaScript, so at runtime there's no object for Angular to use as an injection token. If you try to inject an interface type, Angular has nothing to match against the provider configuration.

Avoid

### Can't inject interface

```
interface UserConfig {
  name: string;
  email: string;
}

@Component({
  selector: 'app-profile',
  template: '<p>Profile</p>',
})
export class UserProfile {
  // ERROR: Interfaces don't exist at runtime
  constructor(private config: UserConfig) {}
}
```

**Solution:** Use [`InjectionToken`](/api/core/InjectionToken) for interface types.

Prefer

### Use InjectionToken for interfaces

```
import {InjectionToken, inject} from '@angular/core';

interface UserConfig {
  name: string;
  email: string;
}

export const USER_CONFIG = new InjectionToken<UserConfig>('user configuration');

// Provide the configuration
bootstrapApplication(App, {
  providers: [
    {
      provide: USER_CONFIG,
      useValue: {name: 'Alice', email: 'alice@example.com'},
    },
  ],
});

// Inject using the token
@Component({
  selector: 'app-profile',
  template: '<p>User: {{config.name}}</p>',
})
export class UserProfile {
  protected config = inject(USER_CONFIG);
}
```

The [`InjectionToken`](/api/core/InjectionToken) exists at runtime and can be used for injection, while the `UserConfig` interface provides type safety during development.

### [Circular dependencies](#circular-dependencies)

Circular dependencies occur when services inject each other, creating a cycle that Angular cannot resolve. For detailed explanations and code examples, see [NG0200: Circular dependency](errors/NG0200).

**Resolution strategies** (in order of preference):

1. **Restructure** - Extract shared logic to a third service, breaking the cycle
2. **Use events** - Replace direct dependencies with event-based communication (such as `Subject`)
3. **Lazy injection** - Use [`Injector.get()`](/api/core/Injector#get) to defer one dependency (last resort)

**NOTE:** Do not use [`forwardRef()`](/api/core/forwardRef) for service circular dependencies—it only solves circular imports in standalone component configurations.

## [Debugging dependency resolution](#debugging-dependency-resolution)

### [Understanding the resolution process](#understanding-the-resolution-process)

Angular resolves dependencies by walking up the injector hierarchy. When a `NullInjectorError` occurs, understanding this search order helps you identify where to add the missing provider.

Angular searches in this order:

1. **Element injector** - The current component or directive
2. **Parent element injectors** - Up the DOM tree through parent components
3. **Environment injector** - The route or application injector
4. **NullInjector** - Throws `NullInjectorError` if not found

When you see a `NullInjectorError`, the service isn't provided at any level the component can access. Check that:

* The service has [`@Service()`](/api/core/Service) or
* The service has `@Injectable({providedIn: 'root'})`, or
* The service is in a `providers` array the component can reach

You can modify this search behavior with resolution modifiers like `self`, `skipSelf`, `host`, and `optional`. For complete coverage of resolution rules and modifiers, see the [Hierarchical injectors guide](guide/di/hierarchical-dependency-injection).

### [Using Angular DevTools](#using-angular-devtools)

Angular DevTools includes an injector tree inspector that visualizes the entire injector hierarchy and shows which providers are available at each level. For installation and general usage, see the [Angular DevTools injector documentation](tools/devtools/injectors).

When debugging DI issues, use DevTools to answer these questions:

* **Is the service provided?** Select the component that fails to inject and check if the service appears in the Injector section.
* **At what level?** Walk up the component tree to find where the service is actually provided (component, route, or application level).
* **Multiple instances?** If a singleton service appears in multiple component injectors, it's likely provided in component `providers` arrays instead of using [`@Service`](/api/core/Service) or `providedIn: 'root'`.

If a service never appears in any injector, verify it has the [`@Service`](/api/core/Service) decorator or is listed in a `providers` array.

### [Logging and tracing injection](#logging-and-tracing-injection)

When DevTools isn't enough, use logging to trace injection behavior.

#### [Logging service creation](#logging-service-creation)

Add console logs to service constructors to see when services are created.

```
import {Service} from '@angular/core';

@Service()
export class UserClient {
  constructor() {
    console.log('UserClient created');
    console.trace(); // Shows call stack
  }

  getUser() {
    return {name: 'Alice'};
  }
}
```

When the service is created, you'll see the log message and a stack trace showing where the injection occurred.

**What to look for:**

* How many times is the constructor called? (should be once for singletons)
* Where in the code is it being injected? (check the stack trace)
* Is it created at the expected time? (application startup vs lazy)

#### [Checking service availability](#checking-service-availability)

Use optional injection with logging to determine if a service is available.

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-debug',
  template: '<p>Debug Component</p>',
})
export class DebugView {
  private userService = inject(UserClient, {optional: true});

  constructor() {
    if (this.userService) {
      console.log('UserClient available:', this.userService);
    } else {
      console.warn('UserClient NOT available');
      console.trace(); // Shows where we tried to inject
    }
  }
}
```

This pattern helps you verify if a service is available without crashing the application.

#### [Logging resolution modifiers](#logging-resolution-modifiers)

Test different resolution strategies with logging.

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-debug',
  template: '<p>Debug Component</p>',
  providers: [UserClient],
})
export class DebugView {
  // Try to get local instance
  private localService = inject(UserClient, {self: true, optional: true});

  // Try to get parent instance
  private parentService = inject(UserClient, {
    skipSelf: true,
    optional: true,
  });

  constructor() {
    console.log('Local instance:', this.localService);
    console.log('Parent instance:', this.parentService);
    console.log('Same instance?', this.localService === this.parentService);
  }
}
```

This shows you which instances are available at different injector levels.

### [Debugging workflow](#debugging-workflow)

When DI fails, follow this systematic approach:

**Step 1: Read the error message**

* Identify the error code (NG0200, NG0203, etc.)
* Read the dependency path
* Note which token failed

**Step 2: Check the basics**

* Does the service have [`@Service`](/api/core/Service) or [`@Injectable()`](/api/core/Injectable)?
* If you use [`@Injectable`](/api/core/Injectable), is `providedIn` set correctly?
* Are imports correct?
* Is the file included in compilation?

**Step 3: Verify injection context**

* Is `inject()` called in a valid context?
* Check for async issues (await, setTimeout, promises)
* Verify timing (not after destroy)

**Step 4: Use debugging tools**

* Open Angular DevTools
* Check injector hierarchy
* Add console logs to constructors
* Use optional injection to test availability

**Step 5: Simplify and isolate**

* Remove dependencies one by one
* Test in a minimal component
* Check each injector level separately
* Create a reproduction case

## [DI error reference](#di-error-reference)

This section provides detailed information about specific Angular DI error codes you may encounter. Use this as a reference when you see these errors in your console.

### [NullInjectorError: No provider for \[Service\]](#nullinjectorerror-no-provider-for-service)

**Error code:** [NG0201](errors/NG0201)

This error occurs when Angular cannot find a provider for a token in the injector hierarchy. The error message includes a dependency path showing where the injection was attempted.

```
NullInjectorError: No provider for UserClient!
  Dependency path: App -> AuthClient -> UserClient
```

The dependency path shows that `App` injected `AuthClient`, which tried to inject `UserClient`, but no provider was found.

#### [Missing the `@Service `or `@Injectable` decorator](#missing-the-service--or-injectable-decorator)

The most common cause is forgetting the [`@Service`](/api/core/Service) or [`@Injectable()`](/api/core/Injectable) decorator on a service class.

Avoid

### Missing decorator

```
export class UserClient {
  getUser() {
    return {name: 'Alice'};
  }
}
```

Angular requires the [`@Service()`](/api/core/Service) decorator to generate the metadata needed for dependency injection.

Prefer

### Include @Service

```
import {Service} from '@angular/core';

@Service()
export class UserClient {
  getUser() {
    return {name: 'Alice'};
  }
}
```

**NOTE:** Classes with zero-argument constructors can work without [`@Service()`](/api/core/Service), but this is not recommended. Always include the decorator for consistency and to avoid issues when adding dependencies later.

#### [Missing providedIn configuration](#missing-providedin-configuration)

A service may have [`@Injectable()`](/api/core/Injectable) but not specify where it should be provided.

Avoid

### No providedIn specified

```
import {Injectable} from '@angular/core';

@Injectable()
export class UserClient {
  getUser() {
    return {name: 'Alice'};
  }
}
```

Use the [`@Service`](/api/core/Service) decorator to make the service available throughout your application.

Prefer

### Specify providedIn

```
import {Service} from '@angular/core';

@Service()
export class UserClient {
  getUser() {
    return {name: 'Alice'};
  }
}
```

The [`@Service`](/api/core/Service) decorator makes the service available application-wide and enables tree-shaking (the service is removed from the bundle if never injected).

#### [Standalone component missing imports](#standalone-component-missing-imports)

In Angular v20+ with standalone components, you must explicitly import or provide dependencies in each component.

Avoid

### Missing service import

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-profile',
  template: '<p>User: {{user().name}}</p>',
})
export class UserProfile {
  private userService = inject(UserClient); // ERROR: No provider
  user = this.userService.getUser();
}
```

Ensure the service uses [`@Service`](/api/core/Service) or add it to the component's `providers` array.

Prefer

### Service uses providedIn: root

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-profile',
  template: '<p>User: {{user().name}}</p>',
})
export class UserProfile {
  private userService = inject(UserClient); // Works: providedIn: 'root'
  user = this.userService.getUser();
}
```

#### [Debugging with the dependency path](#debugging-with-the-dependency-path)

The dependency path in the error message shows the chain of injections that led to the failure.

```
NullInjectorError: No provider for LoggerStore!
  Dependency path: App -> DataStore -> ApiClient -> LoggerStore
```

This path tells you:

1. `App` injected `DataStore`
2. `DataStore` injected `ApiClient`
3. `ApiClient` tried to inject `LoggerStore`
4. No provider for `LoggerStore` was found

Start your investigation at the end of the chain (`LoggerStore`) and verify it has proper configuration.

#### [Checking provider availability with optional injection](#checking-provider-availability-with-optional-injection)

Use optional injection to check if a provider exists without throwing an error.

```
import {Component, inject} from '@angular/core';
import {UserClient} from './user-client';

@Component({
  selector: 'app-debug',
  template: '<p>Service available: {{serviceAvailable}}</p>',
})
export class DebugView {
  private userService = inject(UserClient, {optional: true});
  serviceAvailable = this.userService !== null;
}
```

Optional injection returns `null` if no provider is found, allowing you to handle the absence gracefully.

### [NG0203: inject() must be called from an injection context](#ng0203-inject-must-be-called-from-an-injection-context)

**Error code:** NG0203

This error occurs when you call `inject()` outside of a valid injection context. Angular requires `inject()` to be called synchronously during class construction or factory execution.

```
NG0203: inject() must be called from an injection context such as a
constructor, a factory function, a field initializer, or a function
used with `runInInjectionContext`.
```

#### [Valid injection contexts](#valid-injection-contexts)

Angular allows `inject()` in these locations:

1. **Class field initializers**

   ```
   import {Component, inject} from '@angular/core';
   import {UserClient} from './user-client';

   @Component({
     selector: 'app-profile',
     template: '<p>User: {{user().name}}</p>',
   })
   export class UserProfile {
     private userService = inject(UserClient); // Valid
     user = this.userService.getUser();
   }
   ```

2. **Class constructor**

   ```
   import {Component, inject} from '@angular/core';
   import {UserClient} from './user-client';

   @Component({
     selector: 'app-profile',
     template: '<p>User: {{user().name}}</p>',
   })
   export class UserProfile {
     private userService: UserClient;
     user: ReturnType<UserClient['getUser']>;

     constructor() {
       this.userService = inject(UserClient); // Valid
       this.user = this.userService.getUser();
     }
   }
   ```

3. **Provider factory functions**

   ```
   import {inject, InjectionToken} from '@angular/core';
   import {UserClient} from './user-client';

   export const GREETING = new InjectionToken<string>('greeting', {
     factory() {
       const userService = inject(UserClient); // Valid
       const user = userService.getUser();
       return `Hello, ${user.name}`;
     },
   });
   ```

4. **Inside runInInjectionContext()**

   ```
   import {Component, inject, Injector, runInInjectionContext} from '@angular/core';
   import {UserClient} from './user-client';

   @Component({
     selector: 'app-profile',
     template: '<button (click)="loadUser()">Load User</button>',
   })
   export class UserProfile {
     private injector = inject(Injector);

     loadUser() {
       runInInjectionContext(this.injector, () => {
         const userService = inject(UserClient); // Valid
         console.log(userService.getUser());
       });
     }
   }
   ```

Other injection contexts that `inject()` also works in include:

* [provideAppInitializer](api/core/provideAppInitializer)
* [provideEnvironmentInitializer](api/core/provideEnvironmentInitializer)
* Functional [route guards](guide/routing/route-guards)
* Functional [data resolvers](guide/routing/data-resolvers)
* Route [resources](guide/routing/data-fetching-with-resources)

#### [When this error occurs](#when-this-error-occurs)

This error occurs when:

* Calling `inject()` in lifecycle hooks (`ngOnInit`, `ngAfterViewInit`, etc.)
* Calling `inject()` after `await` in async functions
* Calling `inject()` in callbacks (`setTimeout`, `Promise.then()`, etc.)
* Calling `inject()` outside of class construction phase

See the "Incorrect inject() usage" section for detailed examples and solutions.

#### [Solutions and workarounds](#solutions-and-workarounds)

**Solution 1:** Capture dependencies in field initializers (most common)

```
private userService = inject(UserClient) // Capture at class level
```

**Solution 2:** Use [`runInInjectionContext()`](/api/core/runInInjectionContext) for callbacks

```
private injector = inject(Injector)

someCallback() {
  runInInjectionContext(this.injector, () => {
    const service = inject(MyClient)
  })
}
```

**Solution 3:** Pass dependencies as parameters instead of injecting them

```
// Instead of injecting inside a callback
setTimeout(() => {
  const service = inject(MyClient) // ERROR
}, 1000)

// Capture first, then use
private service = inject(MyClient)

setTimeout(() => {
  this.service.doSomething() // Use captured reference
}, 1000)
```

### [NG0200: Circular dependency detected](#ng0200-circular-dependency-detected)

**Error code:** NG0200

This error occurs when two or more services depend on each other, creating a circular dependency that Angular cannot resolve.

```
NG0200: Circular dependency in DI detected for AuthClient
  Dependency path: AuthClient -> UserClient -> AuthClient
```

The dependency path shows the cycle: `AuthClient` depends on `UserClient`, which depends back on `AuthClient`.

#### [Understanding the error](#understanding-the-error)

Angular creates service instances by calling their constructors and injecting dependencies. When services depend on each other circularly, Angular cannot determine which to create first.

#### [Common causes](#common-causes)

* Direct circular dependency (Service A → Service B → Service A)
* Indirect circular dependency (Service A → Service B → Service C → Service A)
* Import cycles in module files that also have service dependencies

#### [Resolution strategies](#resolution-strategies)

See the "Circular dependencies" section for detailed examples and solutions:

1. **Restructure** - Extract shared logic to a third service (recommended)
2. **Use events** - Replace direct dependencies with event-based communication
3. **Lazy injection** - Use [`Injector.get()`](/api/core/Injector#get) to defer one dependency (last resort)

Do NOT use [`forwardRef()`](/api/core/forwardRef) for service circular dependencies. It only solves circular imports in component configurations.

### [Other DI error codes](#other-di-error-codes)

For detailed explanations and solutions for these errors, see the [Angular error reference](errors):

| Error Code              | Description                                                                                                                      |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| [NG0204](errors/NG0204) | Can't resolve all parameters - missing [`@Injectable()`](/api/core/Injectable) decorator                                         |
| [NG0205](errors/NG0205) | Injector already destroyed - accessing services after component destruction                                                      |
| [NG0207](errors/NG0207) | EnvironmentProviders in wrong context - using [`provideHttpClient()`](/api/common/http/provideHttpClient) in component providers |

## [Next steps](#next-steps)

When you encounter DI errors, remember to:

1. Read the error message and dependency path carefully
2. Verify basic configuration (decorators, `providedIn`, imports)
3. Check injection context and timing
4. Use DevTools and logging to investigate
5. Simplify and isolate the problem

For a deeper understanding of specific topics on dependency injection, check out:

* [Understanding dependency injection](guide/di) - Core DI concepts and patterns
* [Hierarchical dependency injection](guide/di/hierarchical-dependency-injection) - How the injector hierarchy works
* [Testing with dependency injection](guide/testing) - Using TestBed and mocking dependencies

----
url: https://angular.dev/api/animations/AnimationGroupMetadata
----

```
    
      interface AnimationGroupMetadata extends AnimationMetadata {}
    
    
```

One or more animation or style steps that form this group.

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/api/core/RendererStyleFlags2
----

```
    
      enum RendererStyleFlags2 {}
    
    
```

Marks a style as important.

Marks a style as using dash case naming (this-is-dash-case).

Jump to details

----
url: https://angular.dev/api/animations/AnimationQueryOptions
----

```
    
      interface AnimationQueryOptions extends AnimationOptions {}
    
    
```

True if this query is optional, false if it is required. Default is false. A required query throws an error if no elements are retrieved when the query is executed. An optional query does not.

A maximum total number of results to return from the query. If negative, results are limited from the end of the query list towards the beginning. By default, results are not limited.

Sets a time-delay for initiating an animation action. A number and optional time unit, such as "1s" or "10ms" for one second and 10 milliseconds, respectively.The default unit is milliseconds. Default value is 0, meaning no delay.

A set of developer-defined parameters that modify styling and timing when an animation action starts. An array of key-value pairs, where the provided value is used as a default.

Jump to details

----
url: https://angular.dev/api/core/KeyValueDiffer
----

```
    
      interface KeyValueDiffer<K, V> {
}
    
    
```

Compute a difference between the previous state and the new `object` state.

@paramobject`Map<K, V>`

containing the new value.

@returns`KeyValueChanges<K, V> | null`

an object describing the difference. The return value is only valid until the next `diff()` invocation.

Compute a difference between the previous state and the new `object` state.

@paramobject`{ [key: string]: V; }`

containing the new value.

@returns`KeyValueChanges<string, V> | null`

an object describing the difference. The return value is only valid until the next `diff()` invocation.

Jump to details

----
url: https://angular.dev/reference/configs/file-structure
----

You develop applications in the context of an Angular workspace. A workspace contains the files for one or more projects. A project is the set of files that comprise an application or a shareable library.

The Angular CLI `ng new` command creates a workspace.

```
ng new my-project
```

When you run this command, the CLI installs the necessary Angular npm packages and other dependencies in a new workspace, with a root-level application named *my-project*.

By default, `ng new` creates an initial skeleton application at the root level of the workspace. The skeleton is for a simple welcome application that is ready to run and easy to modify. The root-level application has the same name as the workspace, and the source files reside in the `src/` subfolder of the workspace.

This default behavior is suitable for a typical "multi-repo" development style where each application resides in its own workspace. Beginners and intermediate users are encouraged to use `ng new` to create a separate workspace for each application.

Angular also supports workspaces with [multiple projects](#multiple-projects). This type of development environment is suitable for advanced users who are developing shareable libraries, and for enterprises that use a "monorepo" development style, with a single repository and global configuration for all Angular projects.

To set up a monorepo workspace, you should skip creating the root application. See [Setting up for a multi-project workspace](#multiple-projects) below.

## [Workspace configuration files](#workspace-configuration-files)

All projects within a workspace share a [configuration](reference/configs/workspace-config). The top level of the workspace contains workspace-wide configuration files, configuration files for the root-level application, and subfolders for the root-level application source and test files.

| Workspace configuration files | Purpose                                                                                                                                                                                                                                                                                                          |
| :---------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.editorconfig`               | Configuration for code editors. See [EditorConfig](https://editorconfig.org).                                                                                                                                                                                                                                    |
| `.gitignore`                  | Specifies intentionally untracked files that [Git](https://git-scm.com) should ignore.                                                                                                                                                                                                                           |
| `README.md`                   | Documentation for the workspace.                                                                                                                                                                                                                                                                                 |
| `angular.json`                | CLI configuration for all projects in the workspace, including configuration options for how to build, serve, and test each project. For details, see [Angular Workspace Configuration](reference/configs/workspace-config).                                                                                     |
| `package.json`                | Configures [npm package dependencies](reference/configs/npm-packages) that are available to all projects in the workspace. See [npm documentation](https://docs.npmjs.com/files/package.json) for the specific format and contents of this file.                                                                 |
| `package-lock.json`           | Provides version information for all packages installed into `node_modules` by the npm client. See [npm documentation](https://docs.npmjs.com/files/package-lock.json) for details.                                                                                                                              |
| `src/`                        | Source files for the root-level application project.                                                                                                                                                                                                                                                             |
| `public/`                     | Contains image and other asset files to be served as static files by the dev server and copied as-is when you build your application.                                                                                                                                                                            |
| `node_modules/`               | Installed [npm packages](reference/configs/npm-packages) for the entire workspace. Workspace-wide `node_modules` dependencies are visible to all projects.                                                                                                                                                       |
| `tsconfig.json`               | The base [TypeScript](https://www.typescriptlang.org) configuration for projects in the workspace. All other configuration files inherit from this base file. For more information, see the [relevant TypeScript documentation](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html#tsconfig-bases). |

## [Application project files](#application-project-files)

By default, the CLI command `ng new my-app` creates a workspace folder named "my-app" and generates a new application skeleton in a `src/` folder at the top level of the workspace. A newly generated application contains source files for a root component and template.

When the workspace file structure is in place, you can use the `ng generate` command on the command line to add functionality and data to the application. This initial root-level application is the *default app* for CLI commands (unless you change the default after creating [additional apps](#multiple-projects)).

For a single-application workspace, the `src` subfolder of the workspace contains the source files (application logic, data, and assets) for the root application. For a multi-project workspace, additional projects in the `projects` folder contain a `project-name/src/` subfolder with the same structure.

### [Application source files](#application-source-files)

Files at the top level of `src/` support running your application. Subfolders contain the application source and application-specific configuration.

| Application support files | Purpose                                                                                                                                                                                                                           |
| :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app/`                    | Contains the component files in which your application logic and data are defined. See details below.                                                                                                                             |
| `index.html`              | The main HTML page that is served when someone visits your site. The CLI automatically adds all JavaScript and CSS files when building your app, so you typically don't need to add any `<script>` or`<link>` tags here manually. |
| `main.ts`                 | The main entry point for your application.                                                                                                                                                                                        |
| `styles.css`              | Global CSS styles applied to the entire application.                                                                                                                                                                              |

Inside the `src` folder, the `app` folder contains your project's logic and data. Angular components, templates, and styles go here.

| `src/app/` files | Purpose                                                                                                                                                                                                                                                                      |
| :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app.config.ts`  | Defines the application configuration that tells Angular how to assemble the application. As you add more providers to the app, they should be declared here. *Only generated when using the `--standalone` option.*                                                         |
| `app.ts`         | Defines the application's root component, named `App`. The view associated with this root component becomes the root of the view hierarchy as you add components and services to your application.                                                                           |
| `app.html`       | Defines the HTML template associated with `App`.                                                                                                                                                                                                                             |
| `app.css`        | Defines the CSS stylesheet for `App`.                                                                                                                                                                                                                                        |
| `app.spec.ts`    | Defines a unit test for `App`.                                                                                                                                                                                                                                               |
| `app-module.ts`  | Defines the root module, named `AppModule`, that tells Angular how to assemble the application. Initially declares only the `App` component. As you add more components to the app, they must be declared here. *Only generated when using the `--standalone false` option.* |
| `app.routes.ts`  | Defines the application's routing configuration.                                                                                                                                                                                                                             |

### [Application configuration files](#application-configuration-files)

Application-specific configuration files for the root application reside at the workspace root level. For a multi-project workspace, project-specific configuration files are in the project root, under `projects/project-name/`.

Project-specific [TypeScript](https://www.typescriptlang.org) configuration files inherit from the workspace-wide `tsconfig.json`.

| Application-specific configuration files | Purpose                                                                                                                                                                                             |
| :--------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tsconfig.app.json`                      | Application-specific [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html), including [Angular compiler options](reference/configs/angular-compiler-options). |
| `tsconfig.spec.json`                     | [TypeScript configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) for application tests.                                                                                  |

## [Multiple projects](#multiple-projects)

A multi-project workspace is suitable for an organization that uses a single repository and global configuration for multiple Angular projects (the "monorepo" model). A multi-project workspace also supports library development.

### [Setting up for a multi-project workspace](#setting-up-for-a-multi-project-workspace)

If you intend to have multiple projects in a workspace, you can skip the initial application generation when you create the workspace, and give the workspace a unique name. The following command creates a workspace with all of the workspace-wide configuration files, but no root-level application.

```
ng new my-workspace --no-create-application
```

You can then generate applications and libraries with names that are unique within the workspace.

```
cd my-workspace
ng generate application my-app
ng generate library my-lib
```

### [Multiple project file structure](#multiple-project-file-structure)

The first explicitly generated application goes into the `projects` folder along with all other projects in the workspace. Newly generated libraries are also added under `projects`. When you create projects this way, the file structure of the workspace is entirely consistent with the structure of the [workspace configuration file](reference/configs/workspace-config), `angular.json`.

```
my-workspace/
├── … (workspace-wide configuration files)
└── projects/ (applications and libraries)
├── my-app/ (an explicitly generated application)
│ └── … (application-specific code and configuration)
└── my-lib/ (a generated library)
└── … (library-specific code and configuration)
```

## [Library project files](#library-project-files)

When you generate a library using the CLI (with a command such as `ng generate library my-lib`), the generated files go into the `projects/` folder of the workspace. For more information about creating your own libraries, see [Creating Libraries](tools/libraries/creating-libraries).

Unlike an application, a library has its own `package.json` configuration file.

Under the `projects/` folder, the `my-lib` folder contains your library code.

| Library source files     | Purpose                                                                                                                                                                                         |
| :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/lib`                | Contains your library project's logic and data. Like an application project, a library project can contain components, services, modules, directives, and pipes.                                |
| `src/public-api.ts`      | Specifies all files that are exported from your library.                                                                                                                                        |
| `ng-package.json`        | Configuration file used by [ng-packagr](https://github.com/ng-packagr/ng-packagr) for building your library.                                                                                    |
| `package.json`           | Configures [npm package dependencies](reference/configs/npm-packages) that are required for this library.                                                                                       |
| `tsconfig.lib.json`      | Library-specific [TypeScript Configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html), including [Angular compiler options](reference/configs/angular-compiler-options). |
| `tsconfig.lib.prod.json` | Library-specific [TypeScript Configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) that is used when building the library in production mode.                         |
| `tsconfig.spec.json`     | [TypeScript Configuration](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html) for the library's unit tests.                                                                       |

----
url: https://angular.dev/api/common/http/HttpHandlerFn
----

```
    
      type HttpHandlerFn = (req: HttpRequest<unknown>) => Observable<HttpEvent<unknown>>
    
    
```

## [Description](#description)

Represents the next interceptor in an interceptor chain, or the real backend if there are no further interceptors.

Most interceptors will delegate to this function, and either modify the outgoing request or the response when it arrives. Within the scope of the current request, however, this function may be called any number of times, for any number of downstream requests. Such downstream requests need not be to the same URL or even the same origin as the current request. It is also valid to not call the downstream handler at all, and process the current request entirely within the interceptor.

This function should only be called within the scope of the request that's currently being intercepted. Once that request is complete, this downstream handler function should not be called.

Jump to details

----
url: https://angular.dev/api/forms/signals/ItemFieldContext
----

```
    
      interface ItemFieldContext<TValue> extends ChildFieldContext<TValue> {
}
    
    
```

The index of the current field in its parent field.

----
url: https://angular.dev/api/platform-browser-dynamic/JitCompilerFactory
----

Ivy JIT mode doesn't require accessing this symbol.

## [API](#api)

```
    
      class JitCompilerFactory implements CompilerFactory {}
    
    
```

@paramdefaultOptions`CompilerOptions[]`

@returns`JitCompilerFactory`

@paramoptions`CompilerOptions[]`

@returns`Compiler`

Jump to details

----
url: https://angular.dev/guide/routing/read-route-state
----

Angular Router allows you to read and use information associated with a route to create responsive and context-aware components.

## [Get information about the current route with ActivatedRoute](#get-information-about-the-current-route-with-activatedroute)

[`ActivatedRoute`](/api/router/ActivatedRoute) is a service from `@angular/router` that provides all the information associated with the current route.

```
import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-product',
})
export class Product {
  private activatedRoute = inject(ActivatedRoute);

  constructor() {
    console.log(this.activatedRoute);
  }
}
```

The [`ActivatedRoute`](/api/router/ActivatedRoute) can provide different information about the route. Some common properties include:

| Property      | Details                                                                                                                                                           |
| :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`         | An `Observable` of the route paths, represented as an array of [`UrlSegment`](/api/router/UrlSegment) objects for each part of the route path.                    |
| `data`        | An `Observable` that contains the `data` object provided for the route. Also contains any resolved values from the resolve guard.                                 |
| `params`      | An `Observable` that contains the required and optional parameters specific to the route.                                                                         |
| `queryParams` | An `Observable` that contains the query parameters available to all routes.                                                                                       |
| `resources`   | An optional record of [`Resource`](/api/core/Resource) instances defined on the route (when [`withRouterResources`](/api/router/withRouterResources) is enabled). |

Check out the [`ActivatedRoute` API docs](/api/router/ActivatedRoute) for a complete list of what you can access with in the route.

## [Understanding route snapshots](#understanding-route-snapshots)

Page navigations are events over time, and you can access the router state at a given time by retrieving a route snapshot.

Route snapshots contain essential information about the route, including its parameters, data, and child routes. In addition, snapshots are static and will not reflect future changes.

Here’s an example of how you’d access a route snapshot:

```
import {ActivatedRoute, ActivatedRouteSnapshot} from '@angular/router';

@Component(/* ... */)
export class UserProfile {
  readonly userId: string;
  private route = inject(ActivatedRoute);

  constructor() {
    // Example URL: https://www.angular.dev/users/123?role=admin&status=active#contact

    // Access route parameters from snapshot
    this.userId = this.route.snapshot.paramMap.get('id');

    // Access multiple route elements
    const snapshot = this.route.snapshot;
    console.log({
      url: snapshot.url, // Array of UrlSegments matched by this route
      // Route parameters object: {id: '123'}
      params: snapshot.params,
      // Query parameters object: {role: 'admin', status: 'active'}
      queryParams: snapshot.queryParams, // Query parameters
    });
  }
}
```

Check out the [`ActivatedRoute` API docs](/api/router/ActivatedRoute) and [`ActivatedRouteSnapshot` API docs](/api/router/ActivatedRouteSnapshot) for a complete list of all properties you can access.

## [Reading parameters on a route](#reading-parameters-on-a-route)

There are two types of parameters that developers can utilize from a route: route and query parameters.

### [Route Parameters](#route-parameters)

Route parameters allow you to pass data to a component through the URL. This is useful when you want to display specific content based on an identifier in the URL, like a user ID or a product ID.

You can [define route parameters](/guide/routing/define-routes#define-url-paths-with-route-parameters) by prefixing the parameter name with a colon (`:`).

```
import {Routes} from '@angular/router';
import {Product} from './product';

const routes: Routes = [{path: 'product/:id', component: Product}];
```

You can access parameters by subscribing to `route.params`.

```
import {Component, inject, signal} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  selector: 'app-product-detail',
  template: `<h1>Product Details: {{ productId() }}</h1>`,
})
export class ProductDetail {
  productId = signal('');
  private activatedRoute = inject(ActivatedRoute);

  constructor() {
    // Access route parameters
    this.activatedRoute.params.subscribe((params) => {
      this.productId.set(params['id']);
    });
  }
}
```

### [Query Parameters](#query-parameters)

[Query parameters](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) provide a flexible way to pass optional data through URLs without affecting the route structure. Unlike route parameters, query parameters can persist across navigation events and are perfect for handling filtering, sorting, pagination, and other stateful UI elements.

```
// Single parameter structure
// /products?category=electronics
router.navigate(['/products'], {
  queryParams: {category: 'electronics'},
});

// Multiple parameters
// /products?category=electronics&sort=price&page=1
router.navigate(['/products'], {
  queryParams: {
    category: 'electronics',
    sort: 'price',
    page: 1,
  },
});
```

You can access query parameters with `route.queryParams`.

Here is an example of a `ProductList` that updates the query parameters that affect how it displays a list of products:

```
import {ActivatedRoute, Router} from '@angular/router';

@Component({
  selector: 'app-product-list',
  template: `
    <div>
      <select (change)="updateSort($event)">
        <option value="price">Price</option>
        <option value="name">Name</option>
      </select>
      <!-- Products list -->
    </div>
  `,
})
export class ProductList {
  private route = inject(ActivatedRoute);
  private router = inject(Router);

  constructor() {
    // Access query parameters reactively
    this.route.queryParams.subscribe((params) => {
      const sort = params['sort'] || 'price';
      const page = Number(params['page']) || 1;
      this.loadProducts(sort, page);
    });
  }

  updateSort(event: Event) {
    const sort = (event.target as HTMLSelectElement).value;
    // Update URL with new query parameter
    this.router.navigate([], {
      queryParams: {sort},
      queryParamsHandling: 'merge', // Preserve other query parameters
    });
  }
}
```

In this example, users can use a select element to sort the product list by name or price. The associated change handler updates the URL’s query parameters, which in turn triggers a change event that can read the updated query parameters and update the product list.

For more information, check out the [official docs on QueryParamsHandling](/api/router/QueryParamsHandling).

### [Matrix Parameters](#matrix-parameters)

Matrix parameters are optional parameters that belong to a specific URL segment, rather than applying to the entire route. Unlike query parameters which appear after a `?` and apply globally, matrix parameters use semicolons (`;`) and are scoped to individual path segments.

Matrix parameters are useful when you need to pass auxiliary data to a specific route segment without affecting the route definition or matching behavior. Like query parameters, they don't need to be defined in your route configuration.

```
// URL format: /path;key=value
// Multiple parameters: /path;key1=value1;key2=value2

// Navigate with matrix parameters
this.router.navigate(['/awesome-products', {view: 'grid', filter: 'new'}]);
// Results in URL: /awesome-products;view=grid;filter=new
```

**Using ActivatedRoute**

```
import {Component, inject} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component(/* ... */)
export class AwesomeProducts {
  private route = inject(ActivatedRoute);

  constructor() {
    // Access matrix parameters via params
    this.route.params.subscribe((params) => {
      const view = params['view']; // e.g., 'grid'
      const filter = params['filter']; // e.g., 'new'
    });
  }
}
```

**NOTE:** As an alternative to using [`ActivatedRoute`](/api/router/ActivatedRoute), matrix parameters are also bound to component inputs when using the [`withComponentInputBinding`](/api/router/withComponentInputBinding).

## [Detect active current route with RouterLinkActive](#detect-active-current-route-with-routerlinkactive)

You can use the [`RouterLinkActive`](/api/router/RouterLinkActive) directive to dynamically style navigation elements based on the current active route. This is common in navigation elements to inform users what the active route is.

```
<nav>
  <a
    class="button"
    routerLink="/about"
    routerLinkActive="active-button"
    ariaCurrentWhenActive="page"
  >
    About
  </a>
  |
  <a
    class="button"
    routerLink="/settings"
    routerLinkActive="active-button"
    ariaCurrentWhenActive="page"
  >
    Settings
  </a>
</nav>
```

In this example, Angular Router will apply the `active-button` class to the correct anchor link and `ariaCurrentWhenActive` to `page` when the URL matches the corresponding [`routerLink`](/api/router/RouterLinkWithHref).

If you need to add multiple classes onto the element, you can use either a space-separated string or an array:

```
<!-- Space-separated string syntax -->
<a routerLink="/user/bob" routerLinkActive="class1 class2">Bob</a>

<!-- Array syntax -->
<a routerLink="/user/bob" [routerLinkActive]="['class1', 'class2']">Bob</a>
```

Setting [`routerLinkActive`](/api/router/RouterLinkActive) alone does not set `aria-current`. Set the `ariaCurrentWhenActive` input, as in the example above, so that visually impaired users (which may not perceive the different styling being applied) can also identify the active link.

### [Route matching strategy](#route-matching-strategy)

By default, [`RouterLinkActive`](/api/router/RouterLinkActive) considers any ancestors in the route a match.

```
<a [routerLink]="['/user/jane']" routerLinkActive="active-link"> User </a>
<a [routerLink]="['/user/jane/role/admin']" routerLinkActive="active-link"> Role </a>
```

When the user visits `/user/jane/role/admin`, both links would have the `active-link` class.

### [Only apply RouterLinkActive on exact route matches](#only-apply-routerlinkactive-on-exact-route-matches)

If you only want to apply the class on an exact match, you need to provide the `routerLinkActiveOptions` directive with a configuration object that contains the value `exact: true`.

```
<a
  [routerLink]="['/user/jane']"
  routerLinkActive="active-link"
  [routerLinkActiveOptions]="{exact: true}"
>
  User
</a>
<a
  [routerLink]="['/user/jane/role/admin']"
  routerLinkActive="active-link"
  [routerLinkActiveOptions]="{exact: true}"
>
  Role
</a>
```

If you want to be more precise in how a route is matched, it’s worth noting that `exact: true` is actually syntactic sugar for the full set of matching options:

```
// `exact: true` is equivalent to
{
  paths: 'exact',
  fragment: 'ignored',
  matrixParams: 'ignored',
  queryParams: 'exact',
}

// `exact: false` is equivalent
{
  paths: 'subset',
  fragment: 'ignored',
  matrixParams: 'ignored',
  queryParams: 'subset',
}
```

For more information, check out the official docs for [isActiveMatchOptions](/api/router/IsActiveMatchOptions).

### [Apply RouterLinkActive to an ancestor](#apply-routerlinkactive-to-an-ancestor)

The RouterLinkActive directive can also be applied to an ancestor element in order to allow developers to style the elements as desired.

```
<div routerLinkActive="active-link" [routerLinkActiveOptions]="{exact: true}">
  <a routerLink="/user/jim">Jim</a>
  <a routerLink="/user/bob">Bob</a>
</div>
```

For more information, check out the [API docs for RouterLinkActive](/api/router/RouterLinkActive).

## [Check if a URL is active](#check-if-a-url-is-active)

The [`isActive`](/api/router/isActive) function returns a computed signal that tracks whether a given URL is currently active in the router. The signal automatically updates as the router state changes.

```
import {Component, inject} from '@angular/core';
import {isActive, Router} from '@angular/router';

@Component({
  template: `
    <div [class.active]="isSettingsActive()">
      <h2>Settings</h2>
    </div>
  `,
})
export class Panel {
  private router = inject(Router);

  isSettingsActive = isActive('/settings', this.router, {
    paths: 'subset',
    queryParams: 'ignored',
    fragment: 'ignored',
    matrixParams: 'ignored',
  });
}
```

----
url: https://angular.dev/reference/migrations/injectable-to-service
----

This schematic converts eligible [`@Injectable`](/api/core/Injectable) classes to the [`@Service`](/api/core/Service) decorator, an ergonomic shorthand for `@Injectable({providedIn: 'root'})`. It is available as of v22.1, and only migrates classes that are considered safe to migrate.

Run the schematic using the following command:

```
ng generate @angular/core:service
```

A class declared with `providedIn: 'root'` becomes a [`@Service`](/api/core/Service) with no options.

#### [Before](#before)

```
import {Injectable} from '@angular/core';

@Injectable({providedIn: 'root'})
export class MyService {}
```

#### [After](#after)

```
import {Service} from '@angular/core';

@Service()
export class MyService {}
```

A class declared without options is not provided automatically, so the migration keeps that behavior with `autoProvided: false`. You remain responsible for adding it to a `providers` array, as before.

#### [Before](#no-options-before)

```
import {Injectable} from '@angular/core';

@Injectable()
export class MyService {}
```

#### [After](#no-options-after)

```
import {Service} from '@angular/core';

@Service({autoProvided: false})
export class MyService {}
```

For more details, see [Opting out of automatic provisioning](guide/di/creating-and-using-services#opting-out-of-automatic-provisioning).

## [Configuration options](#configuration-options)

### [`--path`](#--path)

By default, the migration updates your whole Angular CLI workspace, including test files. You can limit the migration to a specific sub-directory using this option.

```
ng generate @angular/core:service --path src/app/sub-component
```

## [Limitations](#limitations)

To avoid introducing breakages into your app, the schematic skips a class when:

* The class, or the closest ancestor in your own source that defines a constructor, injects dependencies through its constructor.
* The [`@Injectable`](/api/core/Injectable) decorator receives any option other than `providedIn`.
* The `providedIn` option is set to anything other than `'root'`.

When every [`@Injectable`](/api/core/Injectable) in a file migrates, the migration removes the [`Injectable`](/api/core/Injectable) import. A file with both migrated and skipped classes keeps both imports.

If the migration reports that it did not find any files to migrate, it did not change any file. Either it found no [`@Injectable`](/api/core/Injectable) class it could convert, or the code is already migrated. Constructor injection is the most common reason for a skip, and you can run the [migration to the `inject` function](reference/migrations/inject-function) first, then run this one again.

----
url: https://angular.dev/api/forms/signals/FormValueControl
----

```
    
      interface FormValueControl<TValue> extends FormUiControl<TValue> {}
    
    
```

The value is the only required property in this contract. A component that wants to integrate with the [`Field`](/api/forms/signals/Field) directive via this contract, *must* provide a `model()` that will be kept in sync with the value of the bound [`FieldTree`](/api/forms/signals/FieldTree).

The implementing component *must not* define a `checked` property. This is reserved for components that want to integrate with the [`Field`](/api/forms/signals/Field) directive as a checkbox.

A contract for a form control that edits a [`FieldTree`](/api/forms/signals/FieldTree) of type `TValue`. Any component that implements this contract can be used with the [`Field`](/api/forms/signals/Field) directive.

Many of the properties declared on this contract are optional. They do not need to be implemented, but if they are will be kept in sync with the field state of the field bound to the [`Field`](/api/forms/signals/Field) directive.

Jump to details

----
url: https://angular.dev/api/core/ENVIRONMENT_INITIALIZER
----

from v19.0.0, use provideEnvironmentInitializer instead

## [API](#api)

```
    
      const ENVIRONMENT_INITIALIZER: InjectionToken<readonly (() => void)[]>;
    
    
```

Jump to details

----
url: https://angular.dev/guide/routing/data-fetching-with-resources
----

The Angular Router integrates with Angular signals through the `resources` route configuration. This allows you to fetch data reactively using [`Resource`](/api/core/Resource) APIs.

## [Why use route resources?](#why-use-route-resources)

Route resources offer several advantages over traditional [data resolvers](/guide/routing/data-resolvers):

* **Parallel execution**: Route resources across all matched routes load concurrently instead of one route at a time.
* **Non-blocking data loading**: Use [`nonBlocking()`](/api/router/nonBlocking) to activate the route immediately and render loading skeletons or UI states while data loads in the background.
* **Reload without renavigation**: Call `.reload()` on individual resources or update signal parameters to refresh data without rerunning guards or rematching routes.
* **Reactive data fetching**: Resources integrate directly with Angular signals, automatically re-evaluating when signal dependencies change and exposing reactive status signals like `isLoading()` and `error()`.

## [Enabling route resources](#enabling-route-resources)

To enable route resources, provide [`withRouterResources()`](/api/router/withRouterResources) to your router configuration:

```
import {provideRouter, withComponentInputBinding, withRouterResources} from '@angular/router';

bootstrapApplication(App, {
  providers: [provideRouter(routes, withComponentInputBinding(), withRouterResources())],
});
```

**TIP:** Enable [`withComponentInputBinding()`](/api/router/withComponentInputBinding) so the router can bind resolved resources directly to component inputs.

## [Defining route resources](#defining-route-resources)

You define resources on a route with the `resources` function. The function runs in an injection context, so you can use `inject()` to access services, API clients, or stores directly inside the route definition.

```
import {Component, inject, input, resource} from '@angular/core';
import {Routes} from '@angular/router';
import {UserService} from './user.service';

const routes: Routes = [
  {
    path: 'user/:id',
    component: UserProfile,
    resources: (ctx) => {
      const userService = inject(UserService);
      return {
        user: resource({
          params: () => ctx.params()['id'],
          loader: ({params: id}) => userService.getUser(id),
        }),
      };
    },
  },
];

@Component({
  template: `<p>User: {{ user().name }}</p>`,
})
export class UserProfile {
  // The router binds only the value for blocking resources.
  user = input.required<User>();
}
```

### [The `ResourceContext` object](#the-resourcecontext-object)

The `resources` function receives a [`ResourceContext`](/api/router/ResourceContext) that provides access to reactive route signals: `params`, `queryParams`, `fragment`, and `data`.

### [Supported resource implementations](#supported-resource-implementations)

The `resources` function can return any Angular [`Resource`](/api/core/Resource) implementation, such as [`resource()`](/api/core/resource), [`rxResource()`](/api/core/rxjs-interop/rxResource), or a custom resource.

```
import {Routes} from '@angular/router';
import {rxResource} from '@angular/core/rxjs-interop';

const routes: Routes = [
  {
    path: 'user/:id',
    component: UserProfile,
    resources: (ctx) => ({
      user: rxResource({
        params: () => ctx.params()['id'],
        stream: ({params: id}) => fetchUserObservable(id),
      }),
    }),
  },
];
```

**NOTE:** [`rxResource`](/api/core/rxjs-interop/rxResource) uses the `stream` property instead of `loader` to accept a function that returns an Observable.

The `resources` function can also be `async` and return a `Promise` if you need to perform asynchronous setup or dynamic imports before configuring resources:

```
resources: async (ctx) => {
  const injector = inject(Injector);
  const {fetchUserData} = await import('./user-api');
  return {
    user: resource({
      params: () => ctx.params()['id'],
      loader: ({params: id}) => fetchUserData(id),
      injector,
    }),
  };
},
```

## [Fine-grained change tracking with signals](#fine-grained-change-tracking-with-signals)

A resource tracks the signals that its `params` function reads. Read the exact value you need so that the resource refetches only when that value changes:

```
resources: (ctx) => ({
  products: resource({
    // Tracks only the 'category' query parameter
    params: () => ctx.queryParams()['category'],
    loader: ({params: category}) => fetchProducts(category),
  }),
}),
```

A navigation that changes an unrelated query parameter, such as `?sort=desc` or `?page=2`, leaves `category` unchanged, so the resource does not refetch.

**TIP:** Read specific properties, such as `ctx.params()['id']`, instead of returning an entire parameters object, such as `ctx.params()`. The router creates a new object on every navigation, so returning the whole object refetches the resource even when the individual values are unchanged.

## [Parallel execution](#parallel-execution)

Data resolvers execute sequentially from parent route to child route. If a parent route resolver takes 200ms and a child route resolver takes 300ms, the navigation is blocked for 500ms.

Route resources across the matched route hierarchy run concurrently, so the same navigation completes in 300ms, the time of the slowest resource.

## [Blocking and non-blocking resources](#blocking-and-non-blocking-resources)

By default, every resource returned from `resources` is blocking: the router waits until the data is fully loaded before it activates the route and the component.

For a blocking resource, the router binds the resolved value to the component input, so the input type is `T` instead of `Resource<T>`. The component never observes a `loading` state because the router blocks navigation until the resource loads, and it never observes an `error` state because the router cancels the navigation when the resource errors.

To handle loading states in the UI instead, wrap the resource in [`nonBlocking()`](/api/router/nonBlocking). The router activates the component immediately and binds the full `Resource<T>` object to the component input, which gives you access to `isLoading()`, `error()`, and the other resource signals.

```
import {Component, input, Resource, resource} from '@angular/core';
import {Routes, nonBlocking} from '@angular/router';

const routes: Routes = [
  {
    path: 'reports',
    component: Reports,
    resources: () => ({
      reportData: nonBlocking(
        resource({
          loader: () => fetchHeavyReportData(),
        }),
      ),
    }),
  },
];

@Component({
  template: `
    @if (reportData().isLoading()) {
      <p>Loading...</p>
    } @else if (reportData().error()) {
      <p>Error loading report.</p>
    } @else if (reportData().hasValue()) {
      <report-view [data]="reportData().value()" />
    }
  `,
})
export class Reports {
  reportData = input.required<Resource<ReportData>>();
}
```

**NOTE:** If a blocking resource errors, the router cancels the navigation and emits a [`NavigationError`](/api/router/NavigationError) event. A resource wrapped in [`nonBlocking()`](/api/router/nonBlocking) completes the navigation and exposes the failure through its `error()` signal.

### [Redirecting from a resource](#redirecting-from-a-resource)

If a blocking resource needs to redirect the user (for example, if an item is not found), throw a [`RedirectCommand`](/api/router/RedirectCommand) inside the resource loader. The router cancels the current navigation and redirects to the specified URL:

```
import {inject, resource} from '@angular/core';
import {RedirectCommand, Router, Routes} from '@angular/router';

const routes: Routes = [
  {
    path: 'user/:id',
    component: UserProfile,
    resources: (ctx) => {
      const router = inject(Router);

      return {
        user: resource({
          params: () => ctx.params()['id'],
          loader: async ({params: id}) => {
            const user = await fetchUser(id);
            if (!user) {
              throw new RedirectCommand(router.parseUrl('/not-found'));
            }
            return user;
          },
        }),
      };
    },
  },
];
```

## [Reloading resources without renavigation](#reloading-resources-without-renavigation)

With data resolvers, refetching data requires a route navigation (for example, navigating with `onSameUrlNavigation: 'reload'`), which rematches routes and reruns guards and resolvers.

Route resources support two ways to refresh data in place:

1. **Programmatic reload**: Call `.reload()` on the [`Resource`](/api/core/Resource) instance.
2. **Reactive reload**: Update a signal that the resource's `params` function reads, such as an application filter or state signal, which reruns the loader.

Because the router binds only the value of a blocking resource to a component input, read the [`Resource`](/api/core/Resource) instance from [`ActivatedRoute`](/api/router/ActivatedRoute) or [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) when you need to call `.reload()` or inspect status signals:

```
import {Component, inject, input} from '@angular/core';
import {ActivatedRoute} from '@angular/router';

@Component({
  template: `
    <p>User: {{ user().name }}</p>
    <button (click)="refreshUser()">Refresh</button>
  `,
})
export class UserProfile {
  user = input.required<User>();
  private userResource = inject(ActivatedRoute).resources?.['user'];

  refreshUser() {
    // Reloads only this specific resource without renavigating the route
    this.userResource?.reload();
  }
}
```

## [Transitional states during pending navigations](#transitional-states-during-pending-navigations)

While a navigation is pending, the router freezes the resources that it exposes on [`ActivatedRoute`](/api/router/ActivatedRoute), which masks intermediate `loading` and `reloading` states.

If you navigate from `/user/1` to `/user/2` and the router reuses the `UserProfile` component, the component keeps rendering the data from `/user/1` until `/user/2` resolves. The router then unfreezes the resources and the UI transitions directly to the new data with no loading flash.

The router exposes these resources as read-only, even when the `resources` function returns a writable resource such as [`resource()`](/api/core/resource). You can read the resource signals and call `reload()`, but not `set()` or `update()`. A `reload()` call during an active navigation or during rollback recovery returns `false` so that it cannot interrupt the router's transition tracking.

### [Rollback recovery on cancellation](#rollback-recovery-on-cancellation)

If a navigation is cancelled (for example, by a guard), the router reverts the state tree to the previous state. This reversion can cause the resource's signal dependencies, such as route parameters, to revert to their previous values.

Because the parameters changed back, the resource might automatically trigger a new load to fetch data for the old parameters. To prevent flashing a loading state for data that was already visible, the router retains the previous resource snapshot in the UI until the resource has settled in the reverted state.

**TIP:** Forward the `abortSignal` provided by the resource loader to your asynchronous calls (like `fetch`). When the router rolls back parameters or supersedes navigations, the pending request is cleanly aborted: `loader: ({params: id, abortSignal}) => fetchUser(id, {signal: abortSignal})`.

----
url: https://angular.dev/api/core/NgZoneOptions
----

```
    
      interface NgZoneOptions {}
    
    
```

Optionally specify coalescing event change detections or not. Consider the following case.

```
<div (click)="doSomething()">
  <button (click)="doSomethingElse()"></button>
</div>
```

When button is clicked, because of the event bubbling, both event handlers will be called and 2 change detections will be triggered. We can coalesce such kind of events to trigger change detection only once.

By default, this option is set to false, meaning events will not be coalesced, and change detection will be triggered multiple times. If this option is set to true, change detection will be triggered once in the scenario described above.

Optionally specify if [`NgZone#run()`](/api/core/NgZone#run) method invocations should be coalesced into a single change detection.

Consider the following case.

```
for (let i = 0; i < 10; i ++) {
  ngZone.run(() => {
    // do something
  });
}
```

This case triggers the change detection multiple times. With ngZoneRunCoalescing options, all change detections in an event loop trigger only once. In addition, the change detection executes in requestAnimation.

Jump to details

----
url: https://angular.dev/api/core/BaseResourceOptions
----

```
    
      interface BaseResourceOptions<T, R> {}
    
    
```

A reactive function which determines the request to be made. Whenever the request changes, the loader will be triggered to fetch a new value for the resource.

If a params function isn't provided, the loader won't rerun unless the resource is reloaded.

The value which will be returned from the resource when a server value is unavailable, such as when the resource is still loading.

Equality function used to compare the return value of the loader.

Overrides the [`Injector`](/api/core/Injector) used by [`resource`](/api/core/resource).

Identifier used to cache the resource data in the [`TransferState`](/api/core/TransferState) during server-side rendering and to retrieve it on the client side. This value value needs to be identical for both the client and server.

Jump to details

----
url: https://angular.dev/errors/NG05106
----

Angular threw this error because it tried to insert a DOM node next to another node it was tracking, and that node isn't where Angular expects it to be anymore.

Angular keeps track of the DOM nodes it renders so it knows where to insert, move, or remove things later, for example when a `@for` block re-renders. If something outside Angular changes that part of the DOM (removes a node, moves it somewhere else), Angular's internal record goes stale. The next time it tries to insert next to that node, you get this error instead of a confusing native `NotFoundError`.

This can happen because of:

* App code touching the DOM directly ([`ElementRef.nativeElement`](/api/core/ElementRef#nativeElement), `document.querySelector`, `innerHTML`, etc.) instead of going through Angular.
* A browser extension messing with the page, like a translation tool, a grammar checker, or a password manager.
* An edge case in Angular itself, in code that reorders or conditionally renders views (`@for`, `@if`, dynamically created views).

The following example triggers the error:

```
@Component({
  selector: 'app-example',
  template: `@if (show) {
    <span>{{ text }}</span>
  }`,
})
export class Example {
  show = true;
  text = 'hello';
  hostElement = inject(ElementRef).nativeElement;

  ngAfterViewInit() {
    // Removing this node behind Angular's back is what causes the error.
    this.hostElement.querySelector('span').remove();
  }
}
```

## [Debugging the error](/errors/NG05106#debugging-the-error)

The error message tells you which node Angular expected to find, so start there.

* Look for code in this component (or a parent) that touches the DOM directly instead of using Angular APIs, and switch it to use those instead.
* If the error only shows up for some users, or you can reproduce it locally with an extension installed, try an incognito window with extensions turned off.
* If it happens consistently on the same route for many different users, it's probably not an extension (those vary per user) — look for something deterministic in that page instead, like a `@for`/`@if` that reorders or removes content in an unusual way, or a third-party widget embedded on that page.
* If none of the above explains it, please file an issue with a reproduction.

----
url: https://angular.dev/tutorials/first-app/06-property-binding
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/05-inputs)[](/tutorials/first-app/07-dynamic-template-values)

[](/tutorials/first-app/05-inputs)[](/tutorials/first-app/07-dynamic-template-values)

This tutorial lesson demonstrates how to add property binding to a template and use it to pass dynamic data to components.

[](https://www.youtube.com/watch?v=eM3zi_n7lNs\&autoplay=1)

## [What you'll learn](#what-youll-learn)

* Your app has data bindings in the `Home` template.
* Your app sends data from the `Home` to the `HousingLocation`.

## [Conceptual preview of Inputs](#conceptual-preview-of-inputs)

In this lesson, you'll continue the process of sharing data from the parent component to the child component by binding data to those properties in the template using property binding.

Property binding enables you to connect a variable to an [`Input`](/api/core/Input) in an Angular template. The data is then dynamically bound to the [`Input`](/api/core/Input).

For a more in depth explanation, please refer to the [Property binding](/guide/templates/binding#binding-dynamic-properties-and-attributes) guide.

1. ### [Update the `Home` template](#update-the-home-template)

   This step adds property binding to the `<app-housing-location>` tag.

   In the code editor:

   1. Navigate to `src/app/home/home.ts`

   2. In the template property of the [`@Component`](/api/core/Component) decorator, update the code to match the code below:

      ### Add housingLocation property binding

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location [housingLocation]="housingLocation" />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        housingLocation: HousingLocationInfo = {
          id: 9999,
          name: 'Test Home',
          city: 'Test city',
          state: 'ST',
          photo: `${this.baseUrl}/example-house.jpg`,
          availableUnits: 99,
          wifi: true,
          laundry: false,
        };
      }
      ```

      When adding a property binding to a component tag, we use the `[attribute] = "value"` syntax to notify Angular that the assigned value should be treated as a property from the component class and not a string value.

      The value on the right-hand side is the name of the property from the `Home`.

2. ### [Confirm the code still works](#confirm-the-code-still-works)

   1. Save your changes and confirm the app does not have any errors.
   2. Correct any errors before you continue to the next step.

**SUMMARY:** In this lesson, you added a new property binding and passed in a reference to a class property. Now, the `HousingLocation` has access to data that it can use to customize the component's display.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/api/core/ResourceDependencyError
----

```
    
      class ResourceDependencyError extends Error {}
    
    
```

@paramdependency`Resource<unknown>`

@returns`ResourceDependencyError`

The dependency that errored.

Stack trace where extra frames have been removed and zone names added.

Original stack trace with no modifications

Jump to details

----
url: https://angular.dev/errors/NG0600
----

Angular throws this error when a signal is written to (with `set` or `update`) while a [`computed`](/api/core/computed) or a template is being evaluated. Both are expected to only read signals, so writing to one there would change state in the middle of computing it.

The error message says which of the two cases it is.

## [Writing to signals is not allowed in a `computed`](/errors/NG0600#writing-to-signals-is-not-allowed-in-a-computed)

A [`computed`](/api/core/computed) should derive its value from other signals without side effects:

```
count = signal(1);
total = signal(0);

doubled = computed(() => {
  this.total.set(this.count() * 2); // not allowed
  return this.count() * 2;
});
```

If the value only depends on other signals, make it a [`computed`](/api/core/computed) itself instead of storing it:

```
count = signal(1);
total = computed(() => this.count() * 2);
```

Use [`linkedSignal`](/api/core/linkedSignal) if the value should also be writable, or an [`effect`](/api/core/effect) if the write is a side effect of the change.

The same error is thrown for writes inside the computation of a [`linkedSignal`](/api/core/linkedSignal).

## [Writing to signals is not allowed while Angular renders the template](/errors/NG0600#writing-to-signals-is-not-allowed-while-angular-renders-the-template)

Template expressions run during rendering, so a method called from the template must not write to a signal either:

```
@Component({
  selector: 'app-example',
  template: '<p>{{ label() }}</p>',
})
export class Example {
  views = signal(0);

  label() {
    this.views.update((v) => v + 1); // not allowed
    return `Viewed ${this.views()} times`;
  }
}
```

Move the write to where the change actually happens. Writing to signals is allowed in event listeners, lifecycle hooks, output handlers and effects.

## [Using `untracked`](/errors/NG0600#using-untracked)

A write wrapped in [`untracked`](/api/core/untracked) does not throw. Use it only when the write is intentional and can't be moved, and keep in mind that signals read inside [`untracked`](/api/core/untracked) are not tracked either.

## [Debugging the error](/errors/NG0600#debugging-the-error)

The stack trace points to the `set` or `update` call. From there, look for the [`computed`](/api/core/computed), [`linkedSignal`](/api/core/linkedSignal) or template expression that called it.

----
url: https://angular.dev/tutorials/deferrable-views
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/deferrable-views/1-what-are-deferrable-views)

[](/tutorials/deferrable-views/1-what-are-deferrable-views)

This interactive tutorial consists of lessons that introduce the Angular deferrable views concepts.

## [How to use this tutorial](/tutorials/deferrable-views#how-to-use-this-tutorial)

Each step represents a concept in Angular deferrable views. You can do one, or all of them.

If you get stuck, click "Reveal answer" at the top.

Alright, let's [get started](/tutorials/deferrable-views/1-what-are-deferrable-views).

app.ts

9

1

2

3

4

5

6

7

8

›

import {Component} from '@angular/core';



@Component({

selector: 'app-root',

template: \` Welcome to Angular! \`,

})

export class App {}



Preview

Console

Terminal

Installing packages

----
url: https://angular.dev/api/animations/AnimateChildOptions
----

```
    
      interface AnimateChildOptions extends AnimationOptions {}
    
    
```

Sets a time-delay for initiating an animation action. A number and optional time unit, such as "1s" or "10ms" for one second and 10 milliseconds, respectively.The default unit is milliseconds. Default value is 0, meaning no delay.

A set of developer-defined parameters that modify styling and timing when an animation action starts. An array of key-value pairs, where the provided value is used as a default.

Jump to details

----
url: https://angular.dev/api/localize/TargetMessage
----

```
    
      type TargetMessage = string
    
    
```

## [Description](#description)

A string containing a translation target message.

I.E. the message that indicates what will be translated to.

Uses `{$placeholder-name}` to indicate a placeholder.

Jump to details

----
url: https://angular.dev/api/router/IsActiveMatchOptions
----

```
    
      interface IsActiveMatchOptions {}
    
    
```

Defines the strategy for comparing the matrix parameters of two [`UrlTree`](/api/router/UrlTree)s.

The matrix parameter matching is dependent on the strategy for matching the segments. That is, if the `paths` option is set to `'subset'`, only the matrix parameters of the matching segments will be compared.

* `'exact'`: Requires that matching segments also have exact matrix parameter matches.
* `'subset'`: The matching segments in the router's active [`UrlTree`](/api/router/UrlTree) may contain extra matrix parameters, but those that exist in the [`UrlTree`](/api/router/UrlTree) in question must match.
* `'ignored'`: When comparing [`UrlTree`](/api/router/UrlTree)s, matrix params will be ignored.

Defines the strategy for comparing the query parameters of two [`UrlTree`](/api/router/UrlTree)s.

* `'exact'`: the query parameters must match exactly.
* `'subset'`: the active [`UrlTree`](/api/router/UrlTree) may contain extra parameters, but must match the key and value of any that exist in the [`UrlTree`](/api/router/UrlTree) in question.
* `'ignored'`: When comparing [`UrlTree`](/api/router/UrlTree)s, query params will be ignored.

Defines the strategy for comparing the [`UrlSegment`](/api/router/UrlSegment)s of the [`UrlTree`](/api/router/UrlTree)s.

* `'exact'`: all segments in each [`UrlTree`](/api/router/UrlTree) must match.
* `'subset'`: a [`UrlTree`](/api/router/UrlTree) will be determined to be active if it is a subtree of the active route. That is, the active route may contain extra segments, but must at least have all the segments of the [`UrlTree`](/api/router/UrlTree) in question.

- `'exact'`: indicates that the [`UrlTree`](/api/router/UrlTree) fragments must be equal.
- `'ignored'`: the fragments will not be compared when determining if a [`UrlTree`](/api/router/UrlTree) is active.

Jump to details

----
url: https://angular.dev/api/router/GuardsCheckEnd
----

```
    
      class GuardsCheckEnd extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramurlAfterRedirects`string`

@paramstate`RouterStateSnapshot`

@paramshouldActivate`boolean`

@returns`GuardsCheckEnd`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/api/core/EmbeddedViewRef
----

```
    
      abstract class EmbeddedViewRef<C> extends ViewRef {}
    
    
```

The context for this view, inherited from the anchor element.

The root nodes for this embedded view.

## [Description](#description)

Represents an Angular view in a view container. An embedded view can be referenced from a component other than the hosting component whose template defines it, or it can be defined independently by a [`TemplateRef`](/api/core/TemplateRef).

Properties of elements in a view can change, but the structure (number and order) of elements in a view cannot. Change the structure of elements by inserting, moving, or removing nested views in a view container.

## [Usage Notes](#usage-notes)

The following template breaks down into two separate [`TemplateRef`](/api/core/TemplateRef) instances, an outer one and an inner one.

```
Count: {{items.length}}
<ul>
  <li *ngFor="let  item of items">{{item}}</li>
</ul>
```

This is the outer [`TemplateRef`](/api/core/TemplateRef):

```
Count: {{items.length}}
<ul>
  <ng-template ngFor let-item [ngForOf]="items"></ng-template>
</ul>
```

This is the inner [`TemplateRef`](/api/core/TemplateRef):

```
<li>{{item}}</li>
```

The outer and inner [`TemplateRef`](/api/core/TemplateRef) instances are assembled into views as follows:

```
<!-- ViewRef: outer-0 -->
Count: 2
<ul>
  <ng-template view-container-ref></ng-template>
  <!-- ViewRef: inner-1 --><li>first</li><!-- /ViewRef: inner-1 -->
  <!-- ViewRef: inner-2 --><li>second</li><!-- /ViewRef: inner-2 -->
</ul>
<!-- /ViewRef: outer-0 -->
```

Jump to details

----
url: https://angular.dev/api/forms/signals/maxError
----

## [API](/api/forms/signals/maxError#api)

```
function maxError(
  max: number,
  options: WithFieldTree<ValidationErrorOptions>,
): MaxValidationError;
function maxError(
  max: number,
  options?: ValidationErrorOptions | undefined,
): WithoutFieldTree<MaxValidationError>;
```

Create a max value error associated with the target field

@parammax`number`

The max value constraint

@paramoptions`WithFieldTree<ValidationErrorOptions>`

The validation error options

@returns`MaxValidationError`

Create a max value error

@parammax`number`

The max value constraint

@paramoptions`ValidationErrorOptions | undefined`

The optional validation error options

@returns`WithoutFieldTree<MaxValidationError>`

Jump to details

----
url: https://angular.dev/best-practices/skipping-subtrees
----

JavaScript, by default, uses mutable data structures that you can reference from multiple different components. Angular runs change detection over your entire component tree to make sure that the most up-to-date state of your data structures is reflected in the DOM.

Change detection is sufficiently fast for most applications. However, when an application has an especially large component tree, running change detection across the whole application can cause performance issues. You can address this by configuring change detection to only run on a subset of the component tree.

## [Using `OnPush`](#using-onpush)

OnPush is the default change detection strategy in Angular (since v22). It instructs Angular to run change detection for a component subtree **only** when:

* The root component of the subtree receives new inputs as the result of a template binding. Angular compares the current and past value of the input with `Object.is`.
* Angular handles an event *(for example using event binding, output binding, or [`@HostListener`](/api/core/HostListener) )* in the subtree's root component or any of its children whether they are using OnPush change detection or not.

## [Common change detection scenarios](#common-change-detection-scenarios)

This section examines several common change detection scenarios to illustrate Angular's behavior.

### [An event is handled by a component with `Eager` change detection](#an-event-is-handled-by-a-component-with-eager-change-detection)

If Angular handles an event within a component with the `Eager` strategy, the framework executes change detection on the entire component tree. Angular will skip descendant component subtrees with roots using `OnPush`, which have not received new inputs.

As an example, if we set the change detection strategy of `MainComponent` to `OnPush` and the user interacts with a component outside the subtree with root `MainComponent`, Angular will check all the pink components from the diagram below (`AppComponent`, `HeaderComponent`, `SearchComponent`, `ButtonComponent`) unless `MainComponent` receives new inputs:

## [An event is handled by a component with OnPush](#an-event-is-handled-by-a-component-with-onpush)

If Angular handles an event within a component with OnPush strategy, the framework will execute change detection within the entire component tree. Angular will ignore component subtrees with roots using OnPush, which have not received new inputs and are outside the component which handled the event.

As an example, if Angular handles an event within `MainComponent`, the framework will run change detection in the entire component tree. Angular will ignore the subtree with root `LoginComponent` because it has `OnPush` and the event happened outside of its scope.

## [An event is handled by a descendant of a component with OnPush](#an-event-is-handled-by-a-descendant-of-a-component-with-onpush)

If Angular handles an event in a component with OnPush, the framework will execute change detection in the entire component tree, including the component’s ancestors.

As an example, in the diagram below, Angular handles an event in `LoginComponent` which uses OnPush. Angular will invoke change detection in the entire component subtree including `MainComponent` (`LoginComponent`’s parent), even though `MainComponent` has `OnPush` as well. Angular checks `MainComponent` as well because `LoginComponent` is part of its view.

## [New inputs to component with OnPush](#new-inputs-to-component-with-onpush)

Angular will run change detection within a child component with `OnPush` when setting an input property as result of a template binding.

For example, in the diagram below, `AppComponent` passes a new input to `MainComponent`, which has `OnPush`. Angular will run change detection in `MainComponent` but will not run change detection in `LoginComponent`, which also has `OnPush`, unless it receives new inputs as well.

## [Edge cases](#edge-cases)

* **Modifying input properties in TypeScript code**. When you use an API like [`@ViewChild`](/api/core/ViewChild) or [`@ContentChild`](/api/core/ContentChild) to get a reference to a component in TypeScript and manually modify an [`@Input`](/api/core/Input) property, Angular will not automatically run change detection for OnPush components. If you need Angular to run change detection, you can inject [`ChangeDetectorRef`](/api/core/ChangeDetectorRef) in your component and call `changeDetectorRef.markForCheck()` to tell Angular to schedule a change detection.
* **Modifying object references**. In case an input receives a mutable object as value and you modify the object but preserve the reference, Angular will not invoke change detection. That’s the expected behavior because the previous and the current value of the input point to the same reference.

----
url: https://angular.dev/api/platform-browser/CssVarNamespacer
----

```
    
      class CssVarNamespacer {}
    
    
```

Prepends the namespace prefix to a CSS variable name.

@paramname`string`

The CSS variable name to namespace, including the leading `--`.

@returns`string`

The namespaced CSS variable name, including the leading `--`. Returns the input unchanged if no namespace is configured.

Jump to details

----
url: https://angular.dev/guide/templates/pipes
----

## [Overview](#overview)

Pipes are special operators in Angular template expressions that allow you to transform data declaratively in your template. Pipes let you declare a transformation function once and then use that transformation across multiple templates. Angular pipes use the vertical bar character (`|`), inspired by the [Unix pipe](https://en.wikipedia.org/wiki/Pipeline_\(Unix\)).

**NOTE:** Angular's pipe syntax deviates from standard JavaScript, which uses the vertical bar character for the [bitwise OR operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR). Angular template expressions do not support bitwise operators.

Here is an example using some built-in pipes that Angular provides:

```
import {Component} from '@angular/core';
import {CurrencyPipe, DatePipe, TitleCasePipe} from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [CurrencyPipe, DatePipe, TitleCasePipe],
  template: `
    <main>
      <!-- Transform the company name to title-case and
       transform the purchasedOn date to a locale-formatted string -->
      <h1>Purchases from {{ company | titlecase }} on {{ purchasedOn | date }}</h1>

      <!-- Transform the amount to a currency-formatted string -->
      <p>Total: {{ amount | currency }}</p>
    </main>
  `,
})
export class ShoppingCart {
  amount = 123.45;
  company = 'acme corporation';
  purchasedOn = '2024-07-08';
}
```

When Angular renders the component, it will ensure that the appropriate date format and currency are based on the locale of the user. If the user is in the United States, it would render:

```
<main>
  <h1>Purchases from Acme Corporation on Jul 8, 2024</h1>
  <p>Total: $123.45</p>
</main>
```

See the [in-depth guide on i18n](/guide/i18n) to learn more about how Angular localizes values.

### [Built-in Pipes](#built-in-pipes)

Angular includes a set of built-in pipes in the `@angular/common` package:

| Name                                          | Description                                                                                   |
| --------------------------------------------- | --------------------------------------------------------------------------------------------- |
| [`AsyncPipe`](api/common/AsyncPipe)           | Read the value from a `Promise` or an RxJS `Observable`.                                      |
| [`CurrencyPipe`](api/common/CurrencyPipe)     | Transforms a number to a currency string, formatted according to locale rules.                |
| [`DatePipe`](api/common/DatePipe)             | Formats a `Date` value according to locale rules.                                             |
| [`DecimalPipe`](api/common/DecimalPipe)       | Transforms a number into a string with a decimal point, formatted according to locale rules.  |
| [`I18nPluralPipe`](api/common/I18nPluralPipe) | Maps a value to a string that pluralizes the value according to locale rules.                 |
| [`I18nSelectPipe`](api/common/I18nSelectPipe) | Maps a key to a custom selector that returns a desired value.                                 |
| [`JsonPipe`](api/common/JsonPipe)             | Transforms an object to a string representation via `JSON.stringify`, intended for debugging. |
| [`KeyValuePipe`](api/common/KeyValuePipe)     | Transforms Object or Map into an array of key value pairs.                                    |
| [`LowerCasePipe`](api/common/LowerCasePipe)   | Transforms text to all lower case.                                                            |
| [`PercentPipe`](api/common/PercentPipe)       | Transforms a number to a percentage string, formatted according to locale rules.              |
| [`SlicePipe`](api/common/SlicePipe)           | Creates a new Array or String containing a subset (slice) of the elements.                    |
| [`TitleCasePipe`](api/common/TitleCasePipe)   | Transforms text to title case.                                                                |
| [`UpperCasePipe`](api/common/UpperCasePipe)   | Transforms text to all upper case.                                                            |

## [Using pipes](#using-pipes)

Angular's pipe operator uses the vertical bar character (`|`), within a template expression. The pipe operator is a binary operator– the left-hand operand is the value passed to the transformation function, and the right side operand is the name of the pipe and any additional arguments (described below).

```
<p>Total: {{ amount | currency }}</p>
```

In this example, the value of `amount` is passed into the [`CurrencyPipe`](/api/common/CurrencyPipe) where the pipe name is `currency`. It then renders the default currency for the user’s locale.

### [Combining multiple pipes in the same expression](#combining-multiple-pipes-in-the-same-expression)

You can apply multiple transformations to a value by using multiple pipe operators. Angular runs the pipes from left to right.

The following example demonstrates a combination of pipes to display a localized date in all uppercase:

```
<p>The event will occur on {{ scheduledOn | date | uppercase }}.</p>
```

### [Passing parameters to pipes](#passing-parameters-to-pipes)

Some pipes accept parameters to configure the transformation. To specify a parameter, append the pipe name with a colon (`:`) followed by the parameter value.

For example, the [`DatePipe`](/api/common/DatePipe) is able to take parameters to format the date in a specific way.

```
<p>The event will occur at {{ scheduledOn | date: 'hh:mm' }}.</p>
```

Some pipes may accept multiple parameters. You can specify additional parameter values separated by the colon character (`:`).

For example, we can also pass a second optional parameter to control the timezone.

```
<p>The event will occur at {{ scheduledOn | date: 'hh:mm' : 'UTC' }}.</p>
```

## [How pipes work](#how-pipes-work)

Conceptually, pipes are functions that accept an input value and return a transformed value.

```
import {Component} from '@angular/core';
import {CurrencyPipe} from '@angular/common';

@Component({
  selector: 'app-root',
  imports: [CurrencyPipe],
  template: `
    <main>
      <p>Total: {{ amount | currency }}</p>
    </main>
  `,
})
export class AppComponent {
  amount = 123.45;
}
```

In this example:

1. [`CurrencyPipe`](/api/common/CurrencyPipe) is imported from `@angular/common`
2. [`CurrencyPipe`](/api/common/CurrencyPipe) is added to the `imports` array
3. The `amount` data is passed to the `currency` pipe

### [Pipe operator precedence](#pipe-operator-precedence)

The pipe operator has lower precedence than other binary operators, including `+`, `-`, `*`, `/`, `%`, `&&`, `||`, and `??`.

```
<!-- firstName and lastName are concatenated before the result is passed to the uppercase pipe -->
{{ firstName + lastName | uppercase }}
```

The pipe operator has higher precedence than the conditional (ternary) operator.

```
{{ (isAdmin ? 'Access granted' : 'Access denied') | uppercase }}
```

If the same expression were written without parentheses:

```
{{ isAdmin ? 'Access granted' : 'Access denied' | uppercase }}
```

It will be parsed instead as:

```
{{ isAdmin ? 'Access granted' : ('Access denied' | uppercase) }}
```

Always use parentheses in your expressions when operator precedence may be ambiguous.

### [Change detection with pipes](#change-detection-with-pipes)

By default, all pipes are considered `pure`, which means that they only execute when a primitive input value (such as a `String`, `Number`, `Boolean`, or `Symbol`) or an object reference (such as `Array`, `Object`, `Function`, or `Date`) is changed. Pure pipes offer a performance advantage because Angular can avoid calling the transformation function if the passed value has not changed.

As a result, this means that mutations to object properties or array items are not detected unless the entire object or array reference is replaced with a different instance. If you want this level of change detection, refer to [detecting changes within arrays or objects](#detecting-change-within-arrays-or-objects).

## [Creating custom pipes](#creating-custom-pipes)

You can define a custom pipe by implementing a TypeScript class with the [`@Pipe`](/api/core/Pipe) decorator. A pipe must have two things:

* A name, specified in the pipe decorator
* A method named `transform` that performs the value transformation.

The TypeScript class should additionally implement the [`PipeTransform`](/api/core/PipeTransform) interface to ensure that it satisfies the type signature for a pipe.

Here is an example of a custom pipe that transforms strings to kebab case:

### kebab-case.pipe.ts

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'kebabCase',
})
export class KebabCasePipe implements PipeTransform {
  transform(value: string): string {
    return value.toLowerCase().replace(/ /g, '-');
  }
}
```

### [Using the `@Pipe` decorator](#using-the-pipe-decorator)

When creating a custom pipe, import [`Pipe`](/api/core/Pipe) from the `@angular/core` package and use it as a decorator for the TypeScript class.

```
import {Pipe} from '@angular/core';

@Pipe({
  name: 'myCustomTransformation',
})
export class MyCustomTransformationPipe {}
```

The [`@Pipe`](/api/core/Pipe) decorator requires a `name` that controls how the pipe is used in a template.

### [Naming convention for custom pipes](#naming-convention-for-custom-pipes)

The naming convention for custom pipes consists of two conventions:

* `name` - camelCase is recommended. Do not use hyphens.
* `class name` - PascalCase version of the `name` with [`Pipe`](/api/core/Pipe) appended to the end

### [Implement the `PipeTransform` interface](#implement-the-pipetransform-interface)

In addition to the [`@Pipe`](/api/core/Pipe) decorator, custom pipes should always implement the [`PipeTransform`](/api/core/PipeTransform) interface from `@angular/core`.

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'myCustomTransformation',
})
export class MyCustomTransformationPipe implements PipeTransform {}
```

Implementing this interface ensures that your pipe class has the correct structure.

### [Transforming the value of a pipe](#transforming-the-value-of-a-pipe)

Every transformation is invoked by the `transform` method with the first parameter being the value being passed in and the return value being the transformed value.

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'myCustomTransformation',
})
export class MyCustomTransformationPipe implements PipeTransform {
  transform(value: string): string {
    return `My custom transformation of ${value}.`;
  }
}
```

### [Adding parameters to a custom pipe](#adding-parameters-to-a-custom-pipe)

You can add parameters to your transformation by adding additional parameters to the `transform` method:

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'myCustomTransformation',
})
export class MyCustomTransformationPipe implements PipeTransform {
  transform(value: string, format: string): string {
    let msg = `My custom transformation of ${value}.`;

    if (format === 'uppercase') {
      return msg.toUpperCase();
    } else {
      return msg;
    }
  }
}
```

### [Detecting change within arrays or objects](#detecting-change-within-arrays-or-objects)

When you want a pipe to detect changes within arrays or objects, it must be marked as an impure function by passing the `pure` flag with a value of `false`.

**IMPORTANT:** Avoid creating impure pipes unless absolutely necessary, as they can incur a significant performance penalty if used without care.

```
import {Pipe, PipeTransform} from '@angular/core';

@Pipe({
  name: 'joinNamesImpure',
  pure: false,
})
export class JoinNamesImpurePipe implements PipeTransform {
  transform(names: string[]): string {
    return names.join();
  }
}
```

Angular developers often adopt the convention of including `Impure` in the pipe `name` and class name to indicate the potential performance pitfall to other developers.

## [Using pipe logic outside templates](#using-pipe-logic-outside-templates)

Pipes are template operators designed to transform data declaratively in templates. When you need the same transformation logic in a service, a utility class, or any other non-template context, **avoid injecting the pipe class**. Pipes are not designed as injectable services.

### [Extract the logic from custom pipes](#extract-the-logic-from-custom-pipes)

When you create a custom pipe, extract the transformation into a standalone function. Have the pipe's `transform()` method delegate to that function, and import the function directly wherever else you need it.

### kebab-case.ts

```
export function toKebabCase(value: string): string {
  return value.toLowerCase().replace(/ /g, '-');
}
```

Prefer

### kebab-case.pipe.ts

```
import {Pipe, PipeTransform} from '@angular/core';
import {toKebabCase} from './kebab-case';

@Pipe({name: 'kebabCase'})
export class KebabCasePipe implements PipeTransform {
  transform(value: string): string {
    return toKebabCase(value);
  }
}
```

Prefer

### formatter.service.ts

```
import {Service} from '@angular/core';
import {toKebabCase} from './kebab-case';

@Service()
export class FormatterService {
  formatSlug(title: string): string {
    return toKebabCase(title);
  }
}
```

Avoid

### formatter.service.ts

```
import {inject, Service} from '@angular/core';
import {KebabCasePipe} from './kebab-case.pipe';

@Service()
export class FormatterService {
  // Avoid injecting the pipe class into services or other classes.
  private kebabCasePipe = inject(KebabCasePipe);

  formatSlug(title: string): string {
    return this.kebabCasePipe.transform(title);
  }
}
```

### [Use formatting functions for built-in pipes](#use-formatting-functions-for-built-in-pipes)

Angular's built-in locale-aware pipes each have a corresponding standalone formatting function exported from `@angular/common`. Use these functions instead of injecting the pipe class.

| Pipe                                       | Standalone function                            |
| ------------------------------------------ | ---------------------------------------------- |
| [`DatePipe`](/api/common/DatePipe)         | [`formatDate`](/api/common/formatDate)         |
| [`CurrencyPipe`](/api/common/CurrencyPipe) | [`formatCurrency`](/api/common/formatCurrency) |
| [`DecimalPipe`](/api/common/DecimalPipe)   | [`formatNumber`](/api/common/formatNumber)     |
| [`PercentPipe`](/api/common/PercentPipe)   | [`formatPercent`](/api/common/formatPercent)   |

Prefer

```
import {Service, LOCALE_ID, inject} from '@angular/core';
import {formatNumber} from '@angular/common';

@Service()
export class PriceService {
  private locale = inject(LOCALE_ID);

  format(value: number) {
    return formatNumber(value, this.locale, '1.2-2');
  }
}
```

Avoid

```
import {inject, Service} from '@angular/core';
import {DecimalPipe} from '@angular/common';

@Service()
export class PriceService {
  private decimalPipe = inject(DecimalPipe);

  format(value: number) {
    return this.decimalPipe.transform(value, '1.2-2');
  }
}
```

----
url: https://angular.dev/tools/cli/schematics-authoring
----

You can create your own schematics to operate on Angular projects. Library developers typically package schematics with their libraries to integrate them with the Angular CLI. You can also create stand-alone schematics to manipulate the files and constructs in Angular applications as a way of customizing them for your development environment and making them conform to your standards and constraints. Schematics can be chained, running other schematics to perform complex operations.

Manipulating the code in an application has the potential to be both very powerful and correspondingly dangerous. For example, creating a file that already exists would be an error, and if it was applied immediately, it would discard all the other changes applied so far. The Angular Schematics tooling guards against side effects and errors by creating a virtual file system. A schematic describes a pipeline of transformations that can be applied to the virtual file system. When a schematic runs, the transformations are recorded in memory, and only applied in the real file system once they're confirmed to be valid.

## [Schematics concepts](#schematics-concepts)

The public API for schematics defines classes that represent the basic concepts.

* The virtual file system is represented by a [`Tree`](/api/aria/tree/Tree). The [`Tree`](/api/aria/tree/Tree) data structure contains a *base* (a set of files that already exists) and a *staging area* (a list of changes to be applied to the base). When making modifications, you don't actually change the base, but add those modifications to the staging area.

* A `Rule` object defines a function that takes a [`Tree`](/api/aria/tree/Tree), applies transformations, and returns a new [`Tree`](/api/aria/tree/Tree). The main file for a schematic, `index.ts`, defines a set of rules that implement the schematic's logic.

* A transformation is represented by an `Action`. There are four action types: `Create`, `Rename`, `Overwrite`, and `Delete`.

* Each schematic runs in a context, represented by a `SchematicContext` object.

The context object passed into a rule provides access to utility functions and metadata that the schematic might need to work with, including a logging API to help with debugging. The context also defines a *merge strategy* that determines how changes are merged from the staged tree into the base tree. A change can be accepted or ignored, or throw an exception.

### [Defining rules and actions](#defining-rules-and-actions)

When you create a new blank schematic with the [Schematics CLI](#schematics-cli), the generated entry function is a *rule factory*. A `RuleFactory` object defines a higher-order function that creates a `Rule`.

### index.ts

```
import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';

// You don't have to export the function as default.
// You can also have more than one rule factory per file.
export function helloWorld(_options: any): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    return tree;
  };
}
```

Your rules can make changes to your projects by calling external tools and implementing logic. You need a rule, for example, to define how a template in the schematic is to be merged into the hosting project.

Rules can make use of utilities provided with the `@schematics/angular` package. Look for helper functions for working with modules, dependencies, TypeScript, AST, JSON, Angular CLI workspaces and projects, and more.

### index.ts

```
import {
  JsonAstObject,
  JsonObject,
  JsonValue,
  Path,
  normalize,
  parseJsonAst,
  strings,
} from '@angular-devkit/core';
```

### [Defining input options with a schema and interfaces](#defining-input-options-with-a-schema-and-interfaces)

Rules can collect option values from the caller and inject them into templates. The options available to your rules, with their allowed values and defaults, are defined in the schematic's JSON schema file, `<schematic>/schema.json`. Define variable or enumerated data types for the schema using TypeScript interfaces.

The schema defines the types and default values of variables used in the schematic. For example, the hypothetical "Hello World" schematic might have the following schema.

### schema.json

```
{
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "default": "world"
    },
    "useColor": {
      "type": "boolean"
    }
  }
}
```

See examples of schema files for the Angular CLI command schematics in [`@schematics/angular`](https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/application/schema.json).

### [Schematic prompts](#schematic-prompts)

Schematic *prompts* introduce user interaction into schematic execution. Configure schematic options to display a customizable question to the user. The prompts are displayed before the execution of the schematic, which then uses the response as the value for the option. This lets users direct the operation of the schematic without requiring in-depth knowledge of the full spectrum of available options.

The "Hello World" schematic might, for example, ask the user for their name, and display that name in place of the default name "world". To define such a prompt, add an `x-prompt` property to the schema for the `name` variable.

Similarly, you can add a prompt to let the user decide whether the schematic uses color when executing its hello action. The schema with both prompts would be as follows.

### schema.json

```
{
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "default": "world",
      "x-prompt": "What is your name?"
    },
    "useColor": {
      "type": "boolean",
      "x-prompt": "Would you like the response in color?"
    }
  }
}
```

#### [Prompt short-form syntax](#prompt-short-form-syntax)

These examples use a shorthand form of the prompt syntax, supplying only the text of the question. In most cases, this is all that is required. Notice however, that the two prompts expect different types of input. When using the shorthand form, the most appropriate type is automatically selected based on the property's schema. In the example, the `name` prompt uses the `input` type because it is a string property. The `useColor` prompt uses a `confirmation` type because it is a Boolean property. In this case, "yes" corresponds to `true` and "no" corresponds to `false`.

There are three supported input types.

| Input type   | Details                                            |
| :----------- | :------------------------------------------------- |
| confirmation | A yes or no question; ideal for Boolean options.   |
| input        | Textual input; ideal for string or number options. |
| list         | A predefined set of allowed values.                |

In the short form, the type is inferred from the property's type and constraints.

| Property schema   | Prompt type                                |
| :---------------- | :----------------------------------------- |
| "type": "boolean" | confirmation ("yes"=`true`, "no"=`false`)  |
| "type": "string"  | input                                      |
| "type": "number"  | input (only valid numbers accepted)        |
| "type": "integer" | input (only valid numbers accepted)        |
| "enum": \[…]      | list (enum members become list selections) |

In the following example, the property takes an enumerated value, so the schematic automatically chooses the list type, and creates a menu from the possible values.

### schema.json

```
{
  "style": {
    "description": "The file extension or preprocessor to use for style files.",
    "type": "string",
    "default": "css",
    "enum": ["css", "scss", "sass", "less", "styl"],
    "x-prompt": "Which stylesheet format would you like to use?"
  }
}
```

The prompt runtime automatically validates the provided response against the constraints provided in the JSON schema. If the value is not acceptable, the user is prompted for a new value. This ensures that any values passed to the schematic meet the expectations of the schematic's implementation, so that you do not need to add additional checks within the schematic's code.

#### [Prompt long-form syntax](#prompt-long-form-syntax)

The `x-prompt` field syntax supports a long form for cases where you require additional customization and control over the prompt. In this form, the `x-prompt` field value is a JSON object with subfields that customize the behavior of the prompt.

| Field   | Data value                                                                |
| :------ | :------------------------------------------------------------------------ |
| type    | `confirmation`, `input`, or `list` (selected automatically in short form) |
| message | string (required)                                                         |
| items   | string and/or label/value object pair (only valid with type `list`)       |

The following example of the long form is from the JSON schema for the schematic that the CLI uses to [generate applications](https://github.com/angular/angular-cli/blob/ba8a6ea59983bb52a6f1e66d105c5a77517f062e/packages/schematics/angular/application/schema.json#L56). It defines the prompt that lets users choose which style preprocessor they want to use for the application being created. By using the long form, the schematic can provide more explicit formatting of the menu choices.

### schema.json

```
{
  "style": {
    "description": "The file extension or preprocessor to use for style files.",
    "type": "string",
    "default": "css",
    "enum": ["css", "scss", "sass", "less"],
    "x-prompt": {
      "message": "Which stylesheet format would you like to use?",
      "type": "list",
      "items": [
        {"value": "css", "label": "CSS"},
        {"value": "scss", "label": "SCSS [ https://sass-lang.com/documentation/syntax#scss ]"},
        {
          "value": "sass",
          "label": "Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]"
        },
        {"value": "less", "label": "Less [ https://lesscss.org/ ]"}
      ]
    }
  }
}
```

#### [x-prompt schema](#x-prompt-schema)

The JSON schema that defines a schematic's options supports extensions to allow the declarative definition of prompts and their respective behavior. No additional logic or changes are required to the code of a schematic to support the prompts. The following JSON schema is a complete description of the long-form syntax for the `x-prompt` field.

### x-prompt schema

```
{
  "oneOf": [
    {
      "type": "string"
    },
    {
      "type": "object",
      "properties": {
        "type": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "items": {
          "type": "array",
          "items": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "value": {}
                },
                "required": ["value"]
              }
            ]
          }
        }
      },
      "required": ["message"]
    }
  ]
}
```

## [Schematics CLI](#schematics-cli)

Schematics come with their own command-line tool. Install the Schematics command line tool globally:

```
npm install -g @angular-devkit/schematics-cli
```

This installs the `schematics` executable, which you can use to create a new schematics collection in its own project folder, add a new schematic to an existing collection, or extend an existing schematic.

In the following sections, you will create a new schematics collection using the CLI to introduce the files and file structure, and some of the basic concepts.

The most common use of schematics, however, is to integrate an Angular library with the Angular CLI. Do this by creating the schematic files directly within the library project in an Angular workspace, without using the Schematics CLI. See [Schematics for Libraries](tools/cli/schematics-for-libraries).

### [Creating a schematics collection](#creating-a-schematics-collection)

The following command creates a new schematic named `hello-world` in a new project folder of the same name.

```
schematics blank --name=hello-world
```

The `blank` schematic is provided by the Schematics CLI. The command creates a new project folder (the root folder for the collection) and an initial named schematic in the collection.

Go to the collection folder, install your npm dependencies, and open your new collection in your favorite editor to see the generated files. For example, if you are using VS Code:

```
cd hello-world
npm install
npm run build
code .
```

The initial schematic gets the same name as the project folder, and is generated in `src/hello-world`. Add related schematics to this collection, and modify the generated skeleton code to define your schematic's functionality. Each schematic name must be unique within the collection.

### [Running a schematic](#running-a-schematic)

Use the `schematics` command to run a named schematic. Provide the path to the project folder, the schematic name, and any mandatory options, in the following format.

```
schematics <path-to-schematics-project>:<schematics-name> --<required-option>=<value>
```

The path can be absolute or relative to the current working directory where the command is executed. For example, to run the schematic you just generated (which has no required options), use the following command.

```
schematics .:hello-world
```

### [Adding a schematic to a collection](#adding-a-schematic-to-a-collection)

To add a schematic to an existing collection, use the same command you use to start a new schematics project, but run the command inside the project folder.

```
cd hello-world
schematics blank --name=goodbye-world
```

The command generates the new named schematic inside your collection, with a main `index.ts` file and its associated test spec. It also adds the name, description, and factory function for the new schematic to the collection's schema in the `collection.json` file.

## [Collection contents](#collection-contents)

The top level of the root project folder for a collection contains configuration files, a `node_modules` folder, and a `src/` folder. The `src/` folder contains subfolders for named schematics in the collection, and a schema, `collection.json`, which describes the collected schematics. Each schematic is created with a name, description, and factory function.

```
{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "schematics": {
    "hello-world": {
      "description": "A blank schematic.",
      "factory": "./hello-world/index#helloWorld"
    }
  }
}
```

* The `$schema` property specifies the schema that the CLI uses for validation.

* The `schematics` property lists named schematics that belong to this collection. Each schematic has a plain-text description, and points to the generated entry function in the main file.

* The `factory` property points to the generated entry function. In this example, you invoke the `hello-world` schematic by calling the `helloWorld()` factory function.

* The optional [`schema`](/api/forms/signals/schema) property points to a JSON schema file that defines the command-line options available to the schematic.

* The optional `aliases` array specifies one or more strings that can be used to invoke the schematic. For example, the schematic for the Angular CLI "generate" command has an alias "g", that lets you use the command `ng g`.

### [Named schematics](#named-schematics)

When you use the Schematics CLI to create a blank schematics project, the new blank schematic is the first member of the collection, and has the same name as the collection. When you add a new named schematic to this collection, it is automatically added to the `collection.json` schema.

In addition to the name and description, each schematic has a `factory` property that identifies the schematic's entry point. In the example, you invoke the schematic's defined functionality by calling the `helloWorld()` function in the main file, `hello-world/index.ts`.

Each named schematic in the collection has the following main parts.

| Parts         | Details                                                           |
| :------------ | :---------------------------------------------------------------- |
| `index.ts`    | Code that defines the transformation logic for a named schematic. |
| `schema.json` | Schematic variable definition.                                    |
| `schema.d.ts` | Schematic variables.                                              |
| `files/`      | Optional component/template files to replicate.                   |

It is possible for a schematic to provide all of its logic in the `index.ts` file, without additional templates. You can create dynamic schematics for Angular, however, by providing components and templates in the `files` folder, like those in standalone Angular projects. The logic in the index file configures these templates by defining rules that inject data and modify variables.

----
url: https://angular.dev/tools/libraries/creating-libraries
----

This page provides a conceptual overview of how to create and publish new libraries to extend Angular functionality.

If you find that you need to solve the same problem in more than one application (or want to share your solution with other developers), you have a candidate for a library. A simple example might be a button that sends users to your company website, that would be included in all applications that your company builds.

## [Getting started](#getting-started)

Use the Angular CLI to generate a new library skeleton in a new workspace with the following commands.

```
ng new my-workspace --no-create-application
cd my-workspace
ng generate library my-lib
```

### Naming your library

You should be very careful when choosing the name of your library if you want to publish it later in a public package registry such as npm. See [Publishing your library](tools/libraries/creating-libraries#publishing-your-library).

Avoid using a name that is prefixed with `ng-`, such as `ng-library`. The `ng-` prefix is a reserved keyword used by the Angular framework and its libraries. The `ngx-` prefix is preferred as a convention used to denote that the library is suitable for use with Angular. It is also an excellent indication to consumers of the registry to differentiate between libraries of different JavaScript frameworks.

The `ng generate` command creates the `projects/my-lib` folder in your workspace, which contains a component.

**HELPFUL:** For more details on how a library project is structured, refer to the [Library project files](reference/configs/file-structure#library-project-files) section of the [Project File Structure guide](reference/configs/file-structure).

Use the monorepo model to use the same workspace for multiple projects. See [Setting up for a multi-project workspace](reference/configs/file-structure#multiple-projects).

When you generate a new library, the workspace configuration file, `angular.json`, is updated with a project of type `library`.

```
"projects": {
  …
  "my-lib": {
    "root": "projects/my-lib",
    "sourceRoot": "projects/my-lib/src",
    "projectType": "library",
    "prefix": "lib",
    "architect": {
      "build": {
        "builder": "@angular/build:ng-packagr",
        …
```

Build, test, and lint the project with CLI commands:

```
ng build my-lib --configuration development
ng test my-lib
ng lint my-lib
```

Notice that the configured builder for the project is different from the default builder for application projects. This builder, among other things, ensures that the library is always built with the [AOT compiler](tools/cli/aot-compiler).

To make library code reusable you must define a public API for it. This "user layer" defines what is available to consumers of your library. A user of your library should be able to access public functionality (such as service providers and general utility functions) through a single import path.

The public API for your library is maintained in the `public-api.ts` file in your library folder. Anything exported from this file is made public when your library is imported into an application.

Your library should supply documentation (typically a README file) for installation and maintenance.

## [Entry points](#entry-points)

An *entry point* is a module specifier that consumers import from, together with the public API that specifier exposes. Every library has one *primary entry point*, and can add any number of *secondary entry points*.

The primary entry point is the package itself. The `ng-package.json` file at the root of the library configures it, and `lib.entryFile` names the file that defines its public API.

### projects/my-lib/ng-package.json

```
{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/my-lib",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}
```

Consumers import the primary entry point by the package name:

```
import {ThemeService} from 'my-lib';
```

A secondary entry point is a directory inside the library with its own `ng-package.json` file. The path of that directory relative to the library root defines the import subpath. For example, a `button` directory makes `my-lib/button` available to consumers:

```
import {ButtonComponent} from 'my-lib/button';
```

Angular packages use this same structure. For example, `@angular/core` is the primary entry point and `@angular/core/testing` is a secondary entry point of the same package.

**HELPFUL:** For an architectural overview of how entry points enable code splitting and define chunk boundaries in the Angular Package Format, see [Entrypoints and code splitting](tools/libraries/angular-package-format#entrypoints-and-code-splitting).

### [Adding a secondary entry point](#adding-a-secondary-entry-point)

Create a directory inside the library with its own `ng-package.json` and public API file:

```
projects/my-lib/
├── ng-package.json      (primary entry point: my-lib)
├── package.json
├── src/
│   ├── public-api.ts
│   └── lib/ …
└── button/
    ├── ng-package.json  (secondary entry point: my-lib/button)
    └── src/
        ├── public-api.ts
        └── button.ts
```

The secondary `ng-package.json` configures only the entry point itself:

### projects/my-lib/button/ng-package.json

```
{
  "$schema": "../../../node_modules/ng-packagr/ng-entrypoint.schema.json",
  "lib": {
    "entryFile": "src/public-api.ts"
  }
}
```

**IMPORTANT:** A secondary `ng-package.json` accepts only the `lib` options. Package-wide options such as `dest` and `assets` belong in the `ng-package.json` at the library root and apply to every entry point.

You do not need to register the secondary entry point anywhere else. During the build, `ng-packagr` automatically discovers every `ng-package.json` under the library root and derives the import subpath from its directory path (for example, `button/` becomes `my-lib/button` and `testing/harness/` becomes `my-lib/testing/harness`).

To import a secondary entry point from an application in the same workspace, add a wildcard path mapping in the workspace `tsconfig.json`. By default, `ng generate library` maps only the package root, which does not cover subpaths:

### tsconfig.json

```
{
  "compilerOptions": {
    "paths": {
      "my-lib": ["./dist/my-lib"],
      "my-lib/*": ["./dist/my-lib/*"]
    }
  }
}
```

**IMPORTANT:** When importing code from another entry point in the same library, always use its package import path (for example, `import {ThemeService} from 'my-lib'`) instead of a relative path. `ng-packagr` builds each entry point separately in dependency order, so relative imports across entry points and circular dependencies between entry points will fail the build.

## [Refactoring parts of an application into a library](#refactoring-parts-of-an-application-into-a-library)

To make your solution reusable, you need to adjust it so that it does not depend on application-specific code. Here are some things to consider in migrating application functionality to a library.

* Declarations such as components and pipes should be designed as stateless, meaning they don't rely on or alter external variables. If you do rely on state, you need to evaluate every case and decide whether it is application state or state that the library would manage.

* Any observables that the components subscribe to internally should be cleaned up and disposed of during the lifecycle of those components

* Components should expose their interactions through inputs for providing context, and outputs for communicating events to other components

* Check all internal dependencies.

  * For custom classes or interfaces used in components or service, check whether they depend on additional classes or interfaces that also need to be migrated
  * Similarly, if your library code depends on a service, that service needs to be migrated
  * If your library code or its templates depend on other libraries (such as Angular Material, for instance), you must configure your library with those dependencies

* Consider how you provide services to client applications.

  * Services should declare their own providers, rather than declaring providers in the NgModule or a component. Declaring a provider makes that service *tree-shakable*. This practice lets the compiler leave the service out of the bundle if it never gets injected into the application that imports the library. For more about this, see [Tree-shakable providers](guide/di/lightweight-injection-tokens).

  * If you register global service providers expose a `provideXYZ()` provider function.

  * If your library provides optional services that might not be used by all client applications, support proper tree-shaking for that case by using the [lightweight token design pattern](guide/di/lightweight-injection-tokens)

## [Integrating with the CLI using code-generation schematics](#integrating-with-the-cli-using-code-generation-schematics)

A library typically includes *reusable code* that defines components, services, and other Angular artifacts (pipes, directives) that you import into a project. A library is packaged into an npm package for publishing and sharing. This package can also include schematics that provide instructions for generating or transforming code directly in your project, in the same way that the CLI creates a generic new component with `ng generate component`. A schematic that is packaged with a library can, for example, provide the Angular CLI with the information it needs to generate a component that configures and uses a particular feature, or set of features, defined in that library. One example of this is [Angular Material's navigation schematic](https://material.angular.dev/guide/schematics#navigation-schematic) which configures the CDK's [BreakpointObserver](https://material.angular.dev/cdk/layout/overview#breakpointobserver) and uses it with Material's [MatSideNav](https://material.angular.dev/components/sidenav/overview) and [MatToolbar](https://material.angular.dev/components/toolbar/overview) components.

Create and include the following kinds of schematics:

* Include an installation schematic so that `ng add` can add your library to a project
* Include generation schematics in your library so that `ng generate` can scaffold your defined artifacts (components, services, tests) in a project
* Include an update schematic so that `ng update` can update your library's dependencies and provide migrations for breaking changes in new releases

What you include in your library depends on your task. For example, you could define a schematic to create a dropdown that is pre-populated with canned data to show how to add it to an application. If you want a dropdown that would contain different passed-in values each time, your library could define a schematic to create it with a given configuration. Developers could then use `ng generate` to configure an instance for their own application.

Suppose you want to read a configuration file and then generate a form based on that configuration. If that form needs additional customization by the developer who is using your library, it might work best as a schematic. However, if the form will always be the same and not need much customization by developers, then you could create a dynamic component that takes the configuration and generates the form. In general, the more complex the customization, the more useful the schematic approach.

For more information, see [Schematics Overview](tools/cli/schematics) and [Schematics for Libraries](tools/cli/schematics-for-libraries).

## [Publishing your library](#publishing-your-library)

Use the Angular CLI and the npm package manager to build and publish your library as an npm package.

Angular CLI uses a tool called [ng-packagr](https://github.com/ng-packagr/ng-packagr/blob/main/README.md) to create packages from your compiled code that can be published to npm. See [Building libraries with Ivy](tools/libraries/creating-libraries#publishing-libraries) for information on the distribution formats supported by `ng-packagr` and guidance on how to choose the right format for your library.

You should always build libraries for distribution using the `production` configuration. This ensures that generated output uses the appropriate optimizations and the correct package format for npm.

```
ng build my-lib
cd dist/my-lib
npm publish
```

## [Managing assets in a library](#managing-assets-in-a-library)

In your Angular library, the distributable can include additional assets like theming files, Sass mixins, or documentation (like a changelog). For more information [copy assets into your library as part of the build](https://github.com/ng-packagr/ng-packagr/blob/main/docs/copy-assets.md) and [embed assets in component styles](https://github.com/ng-packagr/ng-packagr/blob/main/docs/embed-assets-css.md).

**IMPORTANT:** When including additional assets like Sass mixins or pre-compiled CSS. You need to add these manually to the conditional ["exports"](tools/libraries/angular-package-format#exports) in the `package.json` of the primary entrypoint.

`ng-packagr` will merge handwritten `"exports"` with the auto-generated ones, allowing for library authors to configure additional export subpaths, or custom conditions.

```
"exports": {
  ".": {
    "sass": "./_index.scss",
  },
  "./theming": {
    "sass": "./_theming.scss"
  },
  "./prebuilt-themes/indigo-pink.css": {
    "style": "./prebuilt-themes/indigo-pink.css"
  }
}
```

The above is an extract from the [@angular/material](https://unpkg.com/browse/@angular/material/package.json) distributable.

## [Peer dependencies](#peer-dependencies)

Angular libraries should list any `@angular/*` dependencies the library depends on as peer dependencies. This ensures that when modules ask for Angular, they all get the exact same module. If a library lists `@angular/core` in `dependencies` instead of `peerDependencies`, it might get a different Angular module instead, which would cause your application to break.

## [Using your own library in applications](#using-your-own-library-in-applications)

You don't have to publish your library to the npm package manager to use it in the same workspace, but you do have to build it first.

To use your own library in an application:

* Build the library. You cannot use a library before it is built.

```
ng build my-lib
```

* In your applications, import from the library by name:

```
import {myExport} from 'my-lib';
```

### [Building and rebuilding your library](#building-and-rebuilding-your-library)

The build step is important if you haven't published your library as an npm package and then installed the package back into your application from npm. For instance, if you clone your git repository and run `npm install`, your editor shows the `my-lib` imports as missing if you haven't yet built your library.

**HELPFUL:** When you import something from a library in an Angular application, Angular looks for a mapping between the library name and a location on disk. When you install a library package, the mapping is in the `node_modules` folder. When you build your own library, it has to find the mapping in your `tsconfig` paths.

Generating a library with the Angular CLI automatically adds its path to the `tsconfig` file. The Angular CLI uses the `tsconfig` paths to tell the build system where to find the library.

For more information, see [Path mapping overview](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping).

You can rebuild your library whenever you make changes to it, but this extra step takes time. *Incremental builds* functionality improves the library-development experience. Every time a file is changed a partial build is performed that emits the amended files.

Incremental builds can be run as a background process in your development environment. To take advantage of this feature add the `--watch` flag to the build command:

```
ng build my-lib --watch
```

**IMPORTANT:** The CLI `build` command uses a different builder and invokes a different build tool for libraries than it does for applications.

* The build system for applications, `@angular/build`, is based on `esbuild`, and is included in all new Angular CLI projects
* The build system for libraries is based on `ng-packagr`. It is only added to your dependencies when you add a library using `ng generate library my-lib`.

The two build systems support different things, and even where they support the same things, they do those things differently. This means that the TypeScript source can result in different JavaScript code in a built library than it would in a built application.

For this reason, an application that depends on a library should only use TypeScript path mappings that point to the *built library*. TypeScript path mappings should *not* point to the library source `.ts` files.

### [Linking libraries for local development](#linking-libraries-for-local-development)

This section explains how to use your package manager's local linking feature (such as [`npm link`](https://docs.npmjs.com/cli/v11/commands/npm-link) or [`pnpm link`](https://pnpm.io/cli/link)) to test a standalone Angular library with an external application during local development, without relying on the monorepo workspace structure or publishing to the npm registry.

**NOTE:** If your library and application are in the same Angular workspace (a monorepo setup), the standard monorepo workflow automatically handles the linking and is generally more efficient. This local linking approach is best when:

* You are developing a standalone library and need to test changes with an external, consuming application.
* You are testing library changes in a consuming application outside the monorepo workspace.

#### [Configuring the consuming application](#configuring-the-consuming-application)

To use linked libraries, you need to configure your application's `angular.json` file with the following settings:

```
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "preserveSymlinks": true
          },
          "configurations": {
            "development": {
              "sourceMap": {
                "scripts": true,
                "styles": true,
                "vendor": true
              }
            }
          }
        },
        "serve": {
          "builder": "@angular/build:dev-server",
          "options": {
            "prebundle": {
              "exclude": ["my-lib"]
            }
          }
        }
      }
    }
  }
}
```

**Configuration options explained:**

* `preserveSymlinks: true`: Instructs the build system to follow the symlinks created by your package manager's linking command instead of resolving to the symlink's original location. This is essential to avoid multiple copies of the dependent node packages.
* `sourceMap.vendor`: Enabling vendor source maps (especially `vendor: true`) for easier debugging of linked library code.
* `prebundle.exclude`: By default, the Angular CLI can pre-bundle all node dependencies. Excluding your library ensures that the linked source code is properly watched and rebuilt when changes occur.

## [Publishing libraries](#publishing-libraries)

There are two distribution formats to use when publishing a library:

| Distribution formats      | Details                                                                                                                                                                                                                                                                                                                                  |
| :------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Partial-Ivy (recommended) | Contains portable code that can be consumed by Ivy applications built with any version of Angular from v12 onwards.                                                                                                                                                                                                                      |
| Full-Ivy                  | Contains private Angular Ivy instructions, which are not guaranteed to work across different versions of Angular. This format requires that the library and application are built with the *exact* same version of Angular. This format is useful for environments where all library and application code is built directly from source. |

For publishing to npm use the partial-Ivy format as it is stable between patch versions of Angular.

Avoid compiling libraries with full-Ivy code if you are publishing to npm because the generated Ivy instructions are not part of Angular's public API, and so might change between patch versions.

## [Ensuring library version compatibility](#ensuring-library-version-compatibility)

The Angular version used to build an application should always be the same or greater than the Angular versions used to build any of its dependent libraries. For example, if you had a library using Angular version 13, the application that depends on that library should use Angular version 13 or later. Angular does not support using an earlier version for the application.

If you intend to publish your library to npm, compile with partial-Ivy code by setting `"compilationMode": "partial"` in `tsconfig.prod.json`. This partial format is stable between different versions of Angular, so is safe to publish to npm. Code with this format is processed during the application build using the same version of the Angular compiler, ensuring that the application and all of its libraries use a single version of Angular.

Avoid compiling libraries with full-Ivy code if you are publishing to npm because the generated Ivy instructions are not part of Angular's public API, and so might change between patch versions.

If you've never published a package in npm before, you must create a user account. Read more in [Publishing npm Packages](https://docs.npmjs.com/getting-started/publishing-npm-packages).

## [Consuming partial-Ivy code outside the Angular CLI](#consuming-partial-ivy-code-outside-the-angular-cli)

An application installs many Angular libraries from npm into its `node_modules` directory. However, the code in these libraries cannot be bundled directly along with the built application as it is not fully compiled. To finish compilation, use the Angular linker.

For applications that don't use the Angular CLI, the linker is available as a [Babel](https://babeljs.io) plugin. The plugin is to be imported from `@angular/compiler-cli/linker/babel`.

The Angular linker Babel plugin supports build caching, meaning that libraries only need to be processed by the linker a single time, regardless of other npm operations.

Example of integrating the plugin into a custom [webpack](https://webpack.js.org) build by registering the linker as a [Babel](https://babeljs.io) plugin using [babel-loader](https://webpack.js.org/loaders/babel-loader/#options).

### webpack.config.mjs

```
import linkerPlugin from '@angular/compiler-cli/linker/babel';

export default {
  // ...
  module: {
    rules: [
      {
        test: /\.m?js$/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins: [linkerPlugin],
            compact: false,
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  // ...
};
```

**HELPFUL:** The Angular CLI integrates the linker plugin automatically, so if consumers of your library are using the CLI, they can install Ivy-native libraries from npm without any additional configuration.

----
url: https://angular.dev/tutorials/learn-angular/11-optimizing-images
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/10-deferrable-views)[](/tutorials/learn-angular/12-enable-routing)

[](/tutorials/learn-angular/10-deferrable-views)[](/tutorials/learn-angular/12-enable-routing)

Images are a big part of many applications, and can be a major contributor to application performance problems, including low [Core Web Vitals](https://web.dev/explore/learn-core-web-vitals) scores.

Image optimization can be a complex topic, but Angular handles most of it for you, with the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive.

**NOTE:** Learn more about [image optimization with NgOptimizedImage in the in-depth guide](/guide/image-optimization).

In this activity, you'll learn how to use [`NgOptimizedImage`](/api/common/NgOptimizedImage) to ensure your images are loaded efficiently.

***

1. ### [Import the NgOptimizedImage directive](#import-the-ngoptimizedimage-directive)

   In order to leverage the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive, first import it from the `@angular/common` library and add it to the component `imports` array.

   ```
   import { NgOptimizedImage } from '@angular/common';

   @Component({
     imports: [NgOptimizedImage],
     ...
   })
   ```

2. ### [Update the src attribute to be ngSrc](#update-the-src-attribute-to-be-ngsrc)

   To enable the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive, swap out the `src` attribute for [`ngSrc`](/api/common/NgOptimizedImage). This applies for both static image sources (i.e., `src`) and dynamic image sources (i.e., `[src]`).

   ```
   import { NgOptimizedImage } from '@angular/common';

   @Component({
   template: `     ...
       <li>
         Static Image:
         <img ngSrc="/logo.svg" alt="Angular logo" width="32" height="32" />
       </li>
       <li>
         Dynamic Image:
         <img [ngSrc]="logoUrl" [alt]="logoAlt" width="32" height="32" />
       </li>
       ...
     `,
   imports: [NgOptimizedImage],
   })
   ```

3. ### [Add width and height attributes](#add-width-and-height-attributes)

   Note that in the above code example, each image has both `width` and `height` attributes. In order to prevent [layout shift](https://web.dev/articles/cls), the [`NgOptimizedImage`](/api/common/NgOptimizedImage) directive requires both size attributes on each image.

   In situations where you can't or don't want to specify a static `height` and `width` for images, you can use [the `fill` attribute](/guide/image-optimization#using-fill-mode) to tell the image to act like a "background image", filling its containing element:

   ```
   // Container div has 'position: "relative"'
   <div class="image-container">
     <img ngSrc="www.example.com/image.png" fill />
   </div>
   ```

   **NOTE:** For the `fill` image to render properly, its parent element must be styled with `position: "relative"`, `position: "fixed"`, or `position: "absolute"`.

4. ### [Prioritize important images](#prioritize-important-images)

   One of the most important optimizations for loading performance is to prioritize any image which might be the ["LCP element"](https://web.dev/articles/optimize-lcp), which is the largest on-screen graphical element when the page loads. To optimize your loading times, make sure to add the `priority` attribute to your "hero image" or any other images that you think could be an LCP element.

   ```
   <img ngSrc="www.example.com/image.png" height="600" width="800" priority />
   ```

5. ### [Optional: Use an image loader](#optional-use-an-image-loader)

   [`NgOptimizedImage`](/api/common/NgOptimizedImage) allows you to specify an [image loader](guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage), which tells the directive how to format URLs for your images. Using a loader allows you to define your images with short, relative URLs:

   ```
   providers: [provideImgixLoader('https://my.base.url/')],
   ```

   Final URL will be '<https://my.base.url/image.png>'

   ```
   <img ngSrc="image.png" height="600" width="800" />
   ```

   Image loaders are for more than just convenience--they allow you to use the full capabilities of [`NgOptimizedImage`](/api/common/NgOptimizedImage). Learn more about these optimizations and the built-in loaders for popular CDNs [here](guide/image-optimization#configuring-an-image-loader-for-ngoptimizedimage).

By adding this directive to your workflow, your images are now loading using best practices with the help of Angular 🎉

If you would like to learn more, check out the [documentation for `NgOptimizedImage`](guide/image-optimization). Keep up the great work and let's learn about routing next.

----
url: https://angular.dev/api/core/Injector
----

```
    
      abstract class Injector {
}
    
    
```

@deprecated

from v5 use the new signature Injector.create(options)

@paramproviders`StaticProvider[]`

@paramparent`Injector | undefined`

@returns`Injector`

Creates a new injector instance that provides one or more dependencies, according to a given type or types of [`StaticProvider`](/api/core/StaticProvider).

@paramoptions`{ providers: (any[] | TypeProvider | ValueProvider | ClassProvider | ConstructorProvider | ExistingProvider | FactoryProvider | StaticClassProvider)[]; parent?: Injector | undefined; name?: string | undefined; }`

An object with the following properties:

* `providers`: An array of providers of the [StaticProvider type](api/core/StaticProvider).
* `parent`: (optional) A parent injector.
* `name`: (optional) A developer-defined identifying name for the new injector.

@returns`DestroyableInjector`

The new injector instance.

## [Usage Notes](#usage-notes)

The following example creates a service injector instance.

```
class Square {
          name = 'square';
        }

        const injector = Injector.create({providers: [{provide: Square, deps: []}]});

        const shape: Square = injector.get(Square);
        expect(shape.name).toEqual('square');
        expect(shape instanceof Square).toBe(true);
```

### [Usage example](#usage-example)

```
const injector: Injector = Injector.create({
        providers: [{provide: 'validToken', useValue: 'Value'}],
      });
      expect(injector.get('validToken')).toEqual('Value');
      expect(() => injector.get('invalidToken')).toThrowError();
      expect(injector.get('invalidToken', 'notFound')).toEqual('notFound');
```

[`Injector`](/api/core/Injector) returns itself when given [`Injector`](/api/core/Injector) as a token:

```
const injector = Injector.create({providers: []});
      expect(injector.get(Injector)).toBe(injector);
```

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/toObservable
----

## [API](#api)

```
function toObservable<T>(
  source: Signal<T>,
  options?: ToObservableOptions | undefined,
): Observable<T>;
```

@paramsource`Signal<T>`

@paramoptions`ToObservableOptions | undefined`

@returns`Observable<T>`

## [Description](#description)

Exposes the value of an Angular `Signal` as an RxJS `Observable`. As it reflects a state, the observable will always emit the latest value upon subscription.

The signal's value will be propagated into the `Observable`'s subscribers using an `effect`. Because the `effect` runs asynchronously, the observable is not guaranteed to be immediately synchronized when the signal is updated. If the signal is updated multiple times before the `effect` runs, the observable will only emit the last signal value.

[`toObservable`](/api/core/rxjs-interop/toObservable) must be called in an injection context unless an injector is provided via options.

Jump to details

----
url: https://angular.dev/api/platform-server/INITIAL_CONFIG
----

```
    
      const INITIAL_CONFIG: InjectionToken<PlatformConfig>;
    
    
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpRequest
----

```
    
      class HttpRequest<T> implements HttpRequestOptions {
}
    
    
```

@parammethod`"GET" | "HEAD"`

@paramurl`string`

@paraminit`HttpRequestOptions | undefined`

@returns`HttpRequest`

@parammethod`"DELETE" | "JSONP" | "OPTIONS"`

@paramurl`string`

@paraminit`HttpRequestOptions | undefined`

@returns`HttpRequest`

@parammethod`"POST"`

@paramurl`string`

@parambody`T | null`

@paraminit`HttpRequestOptions | undefined`

@returns`HttpRequest`

@parammethod`"PUT" | "PATCH"`

@paramurl`string`

@parambody`T | null`

@paraminit`HttpRequestOptions | undefined`

@returns`HttpRequest`

@parammethod`string`

@paramurl`string`

@parambody`T | null`

@paraminit`HttpRequestOptions | undefined`

@returns`HttpRequest`

The request body, or `null` if one isn't set.

Bodies are not enforced to be immutable, as they can include a reference to any user-defined data type. However, interceptors should take care to preserve idempotence by treating them as such.

Outgoing headers for this request.

Shared and mutable context that can be used by interceptors

@deprecated

Use `reportUploadProgress` and `reportDownloadProgress` instead

Whether this request should be made in a way that exposes progress events.

Progress events are expensive (change detection runs on each event) and so they should only be requested if the consumer intends to monitor them.

Note: The default [`HttpBackend`](/api/common/http/HttpBackend) based on fetch, does not support progress report for uploads. Set the [`HttpXhrBackend`](/api/common/http/HttpXhrBackend) with [`withXhr()`](/api/common/http/withXhr) if you need this feature.

When set to `true` a request emited against the [`FetchBackend`](/api/common/http/FetchBackend) will throw an error.

Whether this request should be sent with outgoing credentials (cookies).

The credentials mode of the request, which determines how cookies and HTTP authentication are handled. This can affect whether cookies are sent with the request, and how authentication is handled.

When using the fetch implementation and set to `true`, the browser will not abort the associated request if the page that initiated it is unloaded before the request is complete.

Controls how the request will interact with the browser's HTTP cache. This affects whether a response is retrieved from the cache, how it is stored, or if it bypasses the cache altogether.

Indicates the relative priority of the request. This may be used by the browser to decide the order in which requests are dispatched and resources fetched.

The mode of the request, which determines how the request will interact with the browser's security model. This can affect things like CORS (Cross-Origin Resource Sharing) and same-origin policies.

The redirect mode of the request, which determines how redirects are handled. This can affect whether the request follows redirects automatically, or if it fails when a redirect occurs.

The referrer of the request, which can be used to indicate the origin of the request. This is useful for security and analytics purposes. Value is a same-origin URL, "about:client", or the empty string, to set request's referrer.

The integrity metadata of the request, which can be used to ensure the request is made with the expected content. A cryptographic hash of the resource to be fetched by request

The referrer policy of the request, which can be used to specify the referrer information to be included with the request. This can affect the amount of referrer information sent with the request, and can be used to enhance privacy and security.

The expected response type of the server.

This is used to parse the response appropriately before returning it to the requestee.

The outgoing HTTP request method.

Outgoing URL parameters.

To pass a string representation of HTTP parameters in the URL-query-string format, the [`HttpParamsOptions`](/api/common/http/HttpParamsOptions)' `fromString` may be used. For example:

```
new HttpParams({fromString: 'angular=awesome'})
```

The outgoing URL with all URL parameters set.

The HttpTransferCache option for the request

The timeout for the backend HTTP request in ms.

Transform the free-form body into a serialized format suitable for transmission to the server.

@returns`string | ArrayBuffer | Blob | FormData | URLSearchParams | null`

Examine the body and attempt to infer an appropriate MIME type for it.

If no such type can be inferred, this method will return `null`.

@returns`string | null`

@returns`HttpRequest<T>`

@paramupdate`HttpRequestOptions & { body?: T | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }`

@returns`HttpRequest<T>`

@paramupdate`HttpRequestOptions & { body?: V | null | undefined; method?: string | undefined; url?: string | undefined; setHeaders?: { [name: string]: string | string[]; } | undefined; setParams?: { [param: string]: string; } | undefined; }`

@returns`HttpRequest<V>`

## [Description](#description)

An outgoing HTTP request with an optional typed body.

[`HttpRequest`](/api/common/http/HttpRequest) represents an outgoing request, including URL, method, headers, body, and other request configuration options. Instances should be assumed to be immutable. To modify a [`HttpRequest`](/api/common/http/HttpRequest), the `clone` method should be used.

Jump to details

----
url: https://angular.dev/api/forms/NumberValueAccessor
----

```
    
      class NumberValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor {}
    
    
```

## [Description](#description)

The [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing a number value and listening to number input changes. The value accessor is used by the [`FormControlDirective`](/api/forms/FormControlDirective), [`FormControlName`](/api/forms/FormControlName), and [`NgModel`](/api/forms/NgModel) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using a number input with a reactive form.](#using-a-number-input-with-a-reactive-form)

The following example shows how to use a number input with a reactive form.

```
const totalCountControl = new FormControl();
```

```
<input type="number" [formControl]="totalCountControl">
```

Jump to details

----
url: https://angular.dev/api/forms/MaxLengthValidator
----

```
    
      class MaxLengthValidator extends AbstractValidatorDirective {}
    
    
```

Tracks changes to the maximum length bound to this directive.

Determines whether this validator should be active or not based on an input. Base class implementation checks whether an input is defined (if the value is different from `null` and `undefined`). Validator classes that extend this base class can override this function with the logic specific to a particular validator directive.

@paraminput`unknown`

@returns`boolean`

## [Description](#description)

A directive that adds maximum length validation to controls marked with the `maxlength` attribute. The directive is provided with the [`NG_VALIDATORS`](/api/forms/NG_VALIDATORS) multi-provider list.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Adding a maximum length validator](#adding-a-maximum-length-validator)

The following example shows how to add a maximum length validator to an input attached to an ngModel binding.

```
<input name="firstName" ngModel maxlength="25">
```

Jump to details

----
url: https://angular.dev/api/router/Navigation
----

```
    
      interface Navigation {}
    
    
```

The unique identifier of the current navigation.

The target URL passed into the [`Router#navigateByUrl()`](/api/router/Router#navigateByUrl) call before navigation. This is the value before the router has parsed or applied redirects to it.

The initial target URL after being parsed with [`UrlHandlingStrategy.extract()`](/api/router/UrlHandlingStrategy#extract).

The extracted URL after redirects have been applied. This URL may not be available immediately, therefore this property can be `undefined`. It is guaranteed to be set after the [`RoutesRecognized`](/api/router/RoutesRecognized) event fires.

Identifies how this navigation was triggered.

Options that controlled the strategy used for this navigation. See [`NavigationExtras`](/api/router/NavigationExtras).

The previously successful [`Navigation`](/api/router/Navigation) object. Only one previous navigation is available, therefore this previous [`Navigation`](/api/router/Navigation) object has a `null` value for its own `previousNavigation`.

Aborts the navigation if it has not yet been completed or reached the point where routes are being activated. This function is a no-op if the navigation is beyond the point where it can be aborted.

## [Description](#description)

Information about a navigation operation. Retrieve the most recent navigation object with the [Router.currentNavigation() method](api/router/Router#currentNavigation) .

* *id* : The unique identifier of the current navigation.
* *initialUrl* : The target URL passed into the [`Router#navigateByUrl()`](/api/router/Router#navigateByUrl) call before navigation. This is the value before the router has parsed or applied redirects to it.
* *extractedUrl* : The initial target URL after being parsed with [`UrlHandlingStrategy.extract()`](/api/router/UrlHandlingStrategy#extract).
* *finalUrl* : The extracted URL after redirects have been applied. This URL may not be available immediately, therefore this property can be `undefined`. It is guaranteed to be set after the [`RoutesRecognized`](/api/router/RoutesRecognized) event fires.
* *trigger* : Identifies how this navigation was triggered. -- 'imperative'--Triggered by `router.navigateByUrl` or `router.navigate`. -- 'popstate'--Triggered by a popstate event. -- 'hashchange'--Triggered by a hashchange event.
* *extras* : A [`NavigationExtras`](/api/router/NavigationExtras) options object that controlled the strategy used for this navigation.
* *previousNavigation* : The previously successful [`Navigation`](/api/router/Navigation) object. Only one previous navigation is available, therefore this previous [`Navigation`](/api/router/Navigation) object has a `null` value for its own `previousNavigation`.

Jump to details

----
url: https://angular.dev/guide/routing/data-resolvers
----

Data resolvers allow you to fetch data before navigating to a route, ensuring that your components receive the data they need before rendering. This can help prevent the need for loading states and improve the user experience by pre-loading essential data.

**TIP:** If your application uses Angular Signals, consider using [route resources](/guide/routing/data-fetching-with-resources) for reactive data fetching with [`Resource`](/api/core/Resource) APIs and built-in support for non-blocking data loading.

## [What are data resolvers?](#what-are-data-resolvers)

A data resolver is a service that implements the [`ResolveFn`](/api/router/ResolveFn) function. It runs before a route activates and can fetch data from APIs, databases, or other sources. The resolved data becomes available to the component through the [`ActivatedRoute`](/api/router/ActivatedRoute).

Data resolvers have access to [services provided at the route level](guide/di/defining-dependency-providers#route-providers) as well as route-specific information via the `route` argument.

## [Why use data resolvers?](#why-use-data-resolvers)

Data resolvers solve common routing challenges:

* **Prevent empty states**: Components receive data immediately upon loading
* **Better user experience**: No loading spinners for critical data
* **Error handling**: Handle data fetching errors before navigation
* **Data consistency**: Ensure required data is available before rendering which is important for SSR

## [Creating a resolver](#creating-a-resolver)

You create a resolver by writing a function with the [`ResolveFn`](/api/router/ResolveFn) type.

It receives the [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) and [`RouterStateSnapshot`](/api/router/RouterStateSnapshot) as parameters.

Here is a resolver that gets the user information before rendering a route using the [`inject`](api/core/inject) function:

```
import {inject} from '@angular/core';
import {UserStore, SettingsStore} from './user-store';
import type {ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot} from '@angular/router';
import type {User, Settings} from './types';

export const userResolver: ResolveFn<User> = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const userStore = inject(UserStore);
  const userId = route.paramMap.get('id')!;
  return userStore.getUser(userId);
};

export const settingsResolver: ResolveFn<Settings> = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const settingsStore = inject(SettingsStore);
  const userId = route.paramMap.get('id')!;
  return settingsStore.getUserSettings(userId);
};
```

## [Configuring routes with resolvers](#configuring-routes-with-resolvers)

When you want to add one or more data resolvers to a route, you can add it under the `resolve` key in the route configuration. The [`Routes`](/api/router/Routes) type defines the structure for route configurations:

```
import {Routes} from '@angular/router';

export const routes: Routes = [
  {
    path: 'user/:id',
    component: UserDetail,
    resolve: {
      user: userResolver,
      settings: settingsResolver,
    },
  },
];
```

You can learn more about the [`resolve` configuration in the API docs](api/router/Route#resolve).

## [Accessing resolved data in components](#accessing-resolved-data-in-components)

### [Using ActivatedRoute](#using-activatedroute)

You can access the resolved data in a component by accessing the snapshot data from the [`ActivatedRoute`](/api/router/ActivatedRoute) using the [`signal`](/api/core/signal) function:

```
import {Component, inject, computed} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {toSignal} from '@angular/core/rxjs-interop';
import type {User, Settings} from './types';

@Component({
  template: `
    <h1>{{ user().name }}</h1>
    <p>{{ user().email }}</p>
    <div>Theme: {{ settings().theme }}</div>
  `,
})
export class UserDetail {
  private route = inject(ActivatedRoute);
  private data = toSignal(this.route.data, {requireSync: true});
  user = computed(() => this.data().user as User);
  settings = computed(() => this.data().settings as Settings);
}
```

### [Using withComponentInputBinding](#using-withcomponentinputbinding)

A different approach to accessing the resolved data is to use [`withComponentInputBinding()`](/api/router/withComponentInputBinding) when configuring your router with [`provideRouter`](/api/router/provideRouter). This allows resolved data to be passed directly as component inputs:

```
import {bootstrapApplication} from '@angular/platform-browser';
import {provideRouter, withComponentInputBinding} from '@angular/router';
import {routes} from './app.routes';

bootstrapApplication(App, {
  providers: [provideRouter(routes, withComponentInputBinding())],
});
```

With this configuration, you can define inputs in your component that match the resolver keys using the `input` function and [`input.required`](/api/core/input) for required inputs:

```
import {Component, input} from '@angular/core';
import type {User, Settings} from './types';

@Component({
  template: `
    <h1>{{ user().name }}</h1>
    <p>{{ user().email }}</p>
    <div>Theme: {{ settings().theme }}</div>
  `,
})
export class UserDetail {
  user = input.required<User>();
  settings = input.required<Settings>();
}
```

This approach provides better type safety and eliminates the need to inject [`ActivatedRoute`](/api/router/ActivatedRoute) just to access resolved data.

## [Error handling in resolvers](#error-handling-in-resolvers)

In the event of navigation failures, it is important to handle errors gracefully in your data resolvers. Otherwise, a [`NavigationError`](/api/router/NavigationError) will occur and the navigation to the current route will fail which will lead to a poor experience for your users.

There are three primary ways to handle errors with data resolvers:

1. Centralize error handling in [`withNavigationErrorHandler`](/api/router/withNavigationErrorHandler)
2. Manage errors through a subscription to router events
3. Handle errors directly in the resolver

### [Centralize error handling in `withNavigationErrorHandler`](#centralize-error-handling-in-withnavigationerrorhandler)

The [`withNavigationErrorHandler`](api/router/withNavigationErrorHandler) feature provides a centralized way to handle all navigation errors, including those from failed data resolvers. This approach keeps error handling logic in one place and prevents duplicate error handling code across resolvers.

```
import {bootstrapApplication} from '@angular/platform-browser';
import {provideRouter, Router, withNavigationErrorHandler} from '@angular/router';
import {inject} from '@angular/core';
import {routes} from './app.routes';

bootstrapApplication(App, {
  providers: [
    provideRouter(
      routes,
      withNavigationErrorHandler((error) => {
        const router = inject(Router);

        if (error.error?.message) {
          console.error('Navigation error occurred:', error.error.message);
        }

        router.navigate(['/error']);
      }),
    ),
  ],
});
```

With this configuration, your resolvers can focus on data fetching while letting the centralized handler manage error scenarios:

```
export const userResolver: ResolveFn<User> = (route) => {
  const userStore = inject(UserStore);
  const userId = route.paramMap.get('id')!;
  // No need for explicit error handling - let it bubble up
  return userStore.getUser(userId);
};
```

### [Managing errors through a subscription to router events](#managing-errors-through-a-subscription-to-router-events)

You can also handle resolver errors by subscribing to router events and listening for [`NavigationError`](/api/router/NavigationError) events. This approach gives you more granular control over error handling and allows you to implement custom error recovery logic.

```
import {Component, inject, signal} from '@angular/core';
import {Router, NavigationError} from '@angular/router';
import {toSignal} from '@angular/core/rxjs-interop';
import {map} from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    @if (errorMessage()) {
      <div class="error-banner">
        {{ errorMessage() }}
        <button (click)="retryNavigation()">Retry</button>
      </div>
    }
    <router-outlet />
  `,
})
export class App {
  private router = inject(Router);
  private lastFailedUrl = signal('');

  private navigationErrors = toSignal(
    this.router.events.pipe(
      map((event) => {
        if (event instanceof NavigationError) {
          this.lastFailedUrl.set(event.url);

          if (event.error) {
            console.error('Navigation error', event.error);
          }

          return 'Navigation failed. Please try again.';
        }
        return '';
      }),
    ),
    {initialValue: ''},
  );

  errorMessage = this.navigationErrors;

  retryNavigation() {
    if (this.lastFailedUrl()) {
      this.router.navigateByUrl(this.lastFailedUrl());
    }
  }
}
```

This approach is particularly useful when you need to:

* Implement custom retry logic for failed navigation
* Show specific error messages based on the type of failure
* Track navigation failures for analytics purposes

### [Handling errors directly in the resolver](#handling-errors-directly-in-the-resolver)

Here's an updated example of the `userResolver` that logs the error and navigates back to the generic `/users` page using the [`Router`](/api/router/Router) service:

```
import {inject} from '@angular/core';
import {ResolveFn, RedirectCommand, Router} from '@angular/router';
import {catchError, of, EMPTY} from 'rxjs';
import {UserStore} from './user-store';
import type {User} from './types';

export const userResolver: ResolveFn<User | RedirectCommand> = (route) => {
  const userStore = inject(UserStore);
  const router = inject(Router);
  const userId = route.paramMap.get('id')!;

  return userStore.getUser(userId).pipe(
    catchError((error) => {
      console.error('Failed to load user:', error);
      return of(new RedirectCommand(router.parseUrl('/users')));
    }),
  );
};
```

## [Navigation loading considerations](#navigation-loading-considerations)

While data resolvers prevent loading states within components, they introduce a different UX consideration: navigation is blocked while resolvers execute. Users may experience delays between clicking a link and seeing the new route, especially with slow network requests.

**TIP:** To avoid blocking navigation and render components immediately with skeleton or loading indicators, use [non-blocking route resources](/guide/routing/data-fetching-with-resources#blocking-and-non-blocking-resources).

### [Providing navigation feedback](#providing-navigation-feedback)

To improve user experience during resolver execution, you can listen to router events and show loading indicators:

```
import {Component, computed, inject} from '@angular/core';
import {Router} from '@angular/router';

@Component({
  selector: 'app-root',
  template: `
    @if (isNavigating()) {
      <div class="loading-bar">Loading...</div>
    }
    <router-outlet />
  `,
})
export class App {
  private router = inject(Router);
  isNavigating = computed(() => !!this.router.currentNavigation());
}
```

This approach ensures users receive visual feedback that navigation is in progress while resolvers fetch data.

## [Best practices](#best-practices)

* **Keep resolvers lightweight**: Resolvers should fetch essential data only and not everything the page could possibly need
* **Handle errors**: Always remember to handle errors gracefully to provide the best experience possible to users
* **Use caching**: Consider caching resolved data to improve performance
* **Consider navigation UX**: Implement loading indicators for resolver execution since navigation is blocked during data fetching
* **Set reasonable timeouts**: Avoid resolvers that could hang indefinitely and block navigation
* **Type safety**: Use TypeScript interfaces for resolved data

## [Reading parent resolved data in child resolvers](#reading-parent-resolved-data-in-child-resolvers)

Resolvers execute sequentially from parent to child. When a parent route defines a resolver, its resolved data is available to child resolvers that run afterward.

**NOTE:** Because resolvers execute sequentially from parent to child, each nested level adds to the total navigation wait time (a network waterfall). If your child routes do not depend on parent data, consider [route resources](/guide/routing/data-fetching-with-resources), which execute concurrently across all routes.

```
import {inject} from '@angular/core';
import {provideRouter, ActivatedRouteSnapshot} from '@angular/router';
import {userResolver} from './resolvers';
import {UserPosts} from './pages';
import {PostService} from './services';
import type {User} from './types';

provideRouter([
  {
    path: 'users/:id',
    resolve: {user: userResolver}, // user resolver in the parent route
    children: [
      {
        path: 'posts',
        component: UserPosts,
        // route.data.user is available here while this resolver runs
        resolve: {
          posts: (route: ActivatedRouteSnapshot) => {
            const postService = inject(PostService);
            const user = route.parent?.data['user'] as User; // parent data
            const userId = user.id;
            return postService.getPostByUser(userId);
          },
        },
      },
    ],
  },
]);
```

## [Next steps](#next-steps)

----
url: https://angular.dev/ai/mcp
----

The Angular CLI includes a Model Context Protocol (MCP) server that enables AI assistants (like Cursor, Antigravity, JetBrains AI, etc.) to interact directly with the Angular CLI. It provides tools for code generation, workspace analysis, and running builds/tests.

### Integration with Angular AI Agent Skills

If your host environment supports custom Agent Skills (such as Antigravity), you can combine the Angular CLI MCP server with the official [Angular AI Skills](https://angular.dev/ai/agent-skills). While the skills provide the agent with deep instruction-level guidance and coding standards, the MCP server provides the action tools (like compiling, running tests, and analyzing workspaces) to execute those guidelines, resulting in a complete and powerful development agent.

## [Get Started](#get-started)

To use the MCP server, you configure your host environment (IDE or CLI) to run `npx @angular/cli mcp`.

Create a file named `.antigravity/mcp.json` in your project's root:

```
{
  "mcpServers": {
    "angular-cli": {
      "command": "npx",
      "args": ["-y", "@angular/cli", "mcp"]
    }
  }
}
```

Create `.cursor/mcp.json` in the project root (or globally at `~/.cursor/mcp.json`):

```
{
  "mcpServers": {
    "angular-cli": {
      "command": "npx",
      "args": ["-y", "@angular/cli", "mcp"]
    }
  }
}
```

Create `.vscode/mcp.json`:

```
{
  "servers": {
    "angular-cli": {
      "command": "npx",
      "args": ["-y", "@angular/cli", "mcp"]
    }
  }
}
```

## [Available Tools (Default)](#available-tools-default)

When the MCP server is enabled, AI agents have access to the following tools:

| Name                        | Description                                                                                               |
| :-------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `ai_tutor`                  | Launches an interactive AI-powered Angular tutor.                                                         |
| `devserver_start`           | Asynchronously starts a dev server (`ng serve`). Returns immediately.                                     |
| `devserver_stop`            | Stops the dev server.                                                                                     |
| `devserver_wait_for_build`  | Returns the logs of the most recent build in a running dev server.                                        |
| `get_best_practices`        | Retrieves the Angular Best Practices Guide (crucial for standalone components, typed forms, etc.).        |
| `list_projects`             | Lists all applications and libraries in the workspace by reading `angular.json`.                          |
| `onpush_zoneless_migration` | Analyzes code and provides a plan to migrate it to `OnPush` change detection (prerequisite for zoneless). |
| `run_target`                | Executes a configured target (e.g., build, test, lint, e2e, deploy).                                      |
| `search_documentation`      | Searches the official documentation at `https://angular.dev`.                                             |

## [Common Workflows](#common-workflows)

These workflows demonstrate how AI assistants coordinate different MCP tools to automatically achieve complex developer stories.

### [1. Performance Tuning: Zoneless & OnPush Migration](#1-performance-tuning-zoneless--onpush-migration)

The AI agent optimizes change detection performance and migrates components to a zoneless-ready state.

1. **Discover Workspace**: The AI agent calls `list_projects` to locate components, projects, and style/test configurations in the workspace.
2. **Schematic Modernization (Prerequisite)**: The AI agent runs any prerequisite signal migrations using standard `ng generate` commands (e.g., Signal Inputs, Signal Queries).
3. **Plan Migration**: The AI agent calls `onpush_zoneless_migration` with the absolute path of the directory or component file.
4. **Apply Changes**: The AI agent automatically applies the single actionable change returned by the tool to the codebase.
5. **Verify Changes**: The AI agent runs unit tests by calling `run_target` with the target parameter set to `"test"`.
6. **Repeat**: The AI agent calls `onpush_zoneless_migration` again to retrieve the next step, repeating until the tool indicates the migration is complete.

### [2. Feature Development & TDD Loop](#2-feature-development--tdd-loop)

The AI agent automates research, implementation, and verification when developing new features.

1. **API & Syntax Research**: The AI agent uses `search_documentation` to look up Angular APIs or syntax rules (e.g., `@defer` block options).
2. **Load Coding Standards**: The AI agent calls `get_best_practices` with the workspace path to load Angular version-aligned coding rules.
3. **Start Local Dev Server**: The AI agent starts a background server by calling `devserver_start`.
4. **Monitor Build**: The AI agent uses `devserver_wait_for_build` to watch build logs and ensure compilation succeeds as it edits the code.
5. **Write and Execute Tests**: The AI agent identifies the project's test framework (e.g., Jasmine, Jest, Vitest) via `list_projects`, writes the corresponding test file, and runs the tests using `run_target` with `"test"`.
6. **Stop Dev Server**: When finished, the AI agent stops the active dev server by calling `devserver_stop`.

### [3. Developer Onboarding and Learning](#3-developer-onboarding-and-learning)

The AI agent guides the developer through Angular concepts in an interactive sandbox.

1. **Discover Projects**: The AI agent calls `list_projects` to scan the workspace and identify the codebase structure.
2. **Launch Tutor**: The AI agent runs `ai_tutor` to load the curriculum instructions, persona, and tutoring guidelines.
3. **Follow the Curriculum**: The AI agent guides the user through the curriculum, explaining concepts and instructing them on what components to build or modify.
4. **Implement & Verify**: The AI agent helps implement the sandbox code and verifies changes using `run_target` with `"test"` or `"build"`.

## [Command Options](#command-options)

You can pass arguments to the MCP server in the `args` array of your configuration:

* `--read-only`: Only registers tools that do not modify the project.
* `--local-only`: Only registers tools that do not require an internet connection.

Example for read-only mode:

```
"args": ["-y", "@angular/cli", "mcp", "--read-only"]
```

----
url: https://angular.dev/api/localize/MessageId
----

```
    
      type MessageId = string
    
    
```

Jump to details

----
url: https://angular.dev/api/core/Inject
----

```
@Inject(token: string);
@Inject(token: any);
```

## [Usage Notes](#usage-notes)

The following example shows a class constructor that specifies a custom provider of a dependency using the parameter decorator.

When [`@Inject()`](/api/core/Inject) is not present, the injector uses the type annotation of the parameter as the provider.

```
class Engine {}

        @Injectable()
        class Car {
          constructor(public engine: Engine) {} // same as constructor(@Inject(Engine) engine:Engine)
        }

        const injector = Injector.create({
          providers: [
            {provide: Engine, deps: []},
            {provide: Car, deps: [Engine]},
          ],
        });
        expect(injector.get(Car).engine instanceof Engine).toBe(true);
```

Jump to details

----
url: https://angular.dev/api/forms/signals/BaseNgValidationError
----

```
    
      abstract class BaseNgValidationError implements ValidationError {}
    
    
```

@paramoptions`ValidationErrorOptions | undefined`

@returns`BaseNgValidationError`

Identifies the kind of error.

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/guide/di/hierarchical-dependency-injection
----

This guide provides in-depth coverage of Angular's hierarchical dependency injection system, including resolution rules, modifiers, and advanced patterns.

**NOTE:** For basic concepts about injector hierarchy and provider scoping, see the [defining dependency providers guide](guide/di/defining-dependency-providers#injector-hierarchy-in-angular).

## [Types of injector hierarchies](#types-of-injector-hierarchies)

Angular has two injector hierarchies:

| Injector hierarchies                                             | Details                                                                                                                                                                                                                 |
| :--------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`EnvironmentInjector`](/api/core/EnvironmentInjector) hierarchy | Configure an [`EnvironmentInjector`](/api/core/EnvironmentInjector) in this hierarchy using [`@Service()`](/api/core/Service) or `providers` array in [`ApplicationConfig`](/api/core/ApplicationConfig).               |
| `ElementInjector` hierarchy                                      | Created implicitly at each DOM element. An `ElementInjector` is empty by default unless you configure it in the `providers` property on [`@Directive()`](/api/core/Directive) or [`@Component()`](/api/core/Component). |

### NgModule Based Applications

For [`NgModule`](/api/core/NgModule) based applications, you can provide dependencies with the `ModuleInjector` hierarchy using an [`@NgModule()`](/api/core/NgModule) or [`@Injectable()`](/api/core/Injectable) annotation.

### [`EnvironmentInjector`](#environmentinjector)

The [`EnvironmentInjector`](/api/core/EnvironmentInjector) can be configured in one of two ways by using:

* The [`@Service()`](/api/core/Service)
* The [`ApplicationConfig`](/api/core/ApplicationConfig) `providers` array

### Tree-shaking and @Service()

Using the [`@Service()`](/api/core/Service) decorator is preferable to using the [`ApplicationConfig`](/api/core/ApplicationConfig) `providers` array. With [`@Service`](/api/core/Service), optimization tools can perform tree-shaking, which removes services that your application isn't using. This results in smaller bundle sizes.

Tree-shaking is especially useful for a library because the application which uses the library may not have a need to inject it.

[`EnvironmentInjector`](/api/core/EnvironmentInjector) is configured by the [`ApplicationConfig.providers`](/api/core/ApplicationConfig#providers).

Provide services using [`@Service()`](/api/core/Service) as follows:

```
import {Service} from '@angular/core';

@Service() // <--provides this service in the root EnvironmentInjector
export class ItemService {
  name = 'telephone';
}
```

The [`@Service()`](/api/core/Service) or [`@Injectable()`](/api/core/Injectable) decorators identify a service class.

### [ModuleInjector](#moduleinjector)

In the case of [`NgModule`](/api/core/NgModule) based applications, the ModuleInjector can be configured in one of two ways by using:

* The [`@Service()`](/api/core/Service) decorator,
* The [`@Injectable()`](/api/core/Injectable) `providedIn` property to refer to `root` or `platform`
* The [`@NgModule()`](/api/core/NgModule) `providers` array

`ModuleInjector` is configured by the [`@NgModule.providers`](/api/core/NgModule#providers) and [`NgModule.imports`](/api/core/NgModule#imports) property. `ModuleInjector` is a flattening of all the providers arrays that can be reached by following the [`NgModule.imports`](/api/core/NgModule#imports) recursively.

Child `ModuleInjector` hierarchies are created when lazy loading other `@NgModules`.

### [Platform injector](#platform-injector)

There are two more injectors above `root`, an additional [`EnvironmentInjector`](/api/core/EnvironmentInjector) and `NullInjector()`.

Consider how Angular bootstraps the application with the following in `main.ts`:

```
bootstrapApplication(App, appConfig);
```

The [`bootstrapApplication()`](/api/platform-browser/bootstrapApplication) method creates a child injector of the platform injector which is configured by the [`ApplicationConfig`](/api/core/ApplicationConfig) instance. This is the `root` [`EnvironmentInjector`](/api/core/EnvironmentInjector).

The [`bootstrapApplication()`](/api/platform-browser/bootstrapApplication) method also creates (or reuses) the platform injector, which contains platform-specific dependencies. This allows multiple applications to share a platform configuration. For example, a browser has only one URL bar, no matter how many applications you have running. You can configure additional platform-specific providers at the platform level by supplying `extraProviders` using the [`platformBrowser()`](/api/platform-browser/platformBrowser) function.

The next parent injector in the hierarchy is the `NullInjector()`, which is the top of the tree. If you've gone so far up the tree that you are looking for a service in the `NullInjector()`, you'll get an error unless you've used [`@Optional()`](/api/core/Optional) because ultimately, everything ends at the `NullInjector()` and it returns an error or, in the case of [`@Optional()`](/api/core/Optional), `null`. For more information on [`@Optional()`](/api/core/Optional), see the [`@Optional()` section](#optional) of this guide.

The following diagram represents the relationship between the `root` `ModuleInjector` and its parent injectors as the previous paragraphs describe.

While the name `root` is a special alias, other [`EnvironmentInjector`](/api/core/EnvironmentInjector) hierarchies don't have aliases. You have the option to create [`EnvironmentInjector`](/api/core/EnvironmentInjector) hierarchies whenever a dynamically loaded component is created, such as with the Router, which will create child [`EnvironmentInjector`](/api/core/EnvironmentInjector) hierarchies.

All requests forward up to the root injector, whether you configured it with the [`ApplicationConfig`](/api/core/ApplicationConfig) instance passed to the [`bootstrapApplication()`](/api/platform-browser/bootstrapApplication) method, or registered all providers with `root` in their own services.

### @Injectable() vs. ApplicationConfig

If you configure an app-wide provider in the [`ApplicationConfig`](/api/core/ApplicationConfig) of [`bootstrapApplication`](/api/platform-browser/bootstrapApplication), it overrides one configured for `root` in the [`@Injectable()`](/api/core/Injectable) metadata. You can do this to configure a non-default provider of a service that is shared with multiple applications.

Here is an example of the case where the component router configuration includes a non-default [location strategy](guide/routing/common-router-tasks#locationstrategy-and-browser-url-styles) by listing its provider in the `providers` list of the [`ApplicationConfig`](/api/core/ApplicationConfig).

```
providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}];
```

For [`NgModule`](/api/core/NgModule) based applications, configure app-wide providers in the `AppModule` `providers`.

### [`ElementInjector`](#elementinjector)

Angular creates `ElementInjector` hierarchies implicitly for each DOM element.

Providing a service in the [`@Component()`](/api/core/Component) decorator using its `providers` or `viewProviders` property configures an `ElementInjector`. For example, the following `TestComponent` configures the `ElementInjector` by providing the service as follows:

```
@Component({
  /* … */
  providers: [{ provide: ItemService, useValue: { name: 'lamp' } }]
})
export class TestComponent
```

**HELPFUL:** See the [resolution rules](#resolution-rules) section to understand the relationship between the [`EnvironmentInjector`](/api/core/EnvironmentInjector) tree, the `ModuleInjector` and the `ElementInjector` tree.

When you provide services in a component, that service is available by way of the `ElementInjector` at that component instance. It may also be visible at child component/directives based on visibility rules described in the [resolution rules](#resolution-rules) section.

When the component instance is destroyed, so is that service instance.

#### [`@Directive()` and `@Component()`](#directive-and-component)

A component is a special type of directive, which means that just as [`@Directive()`](/api/core/Directive) has a `providers` property, [`@Component()`](/api/core/Component) does too. This means that directives as well as components can configure providers, using the `providers` property. When you configure a provider for a component or directive using the `providers` property, that provider belongs to the `ElementInjector` of that component or directive. Components and directives on the same element share an injector.

## [Resolution rules](#resolution-rules)

When resolving a token for a component/directive, Angular resolves it in two phases:

1. Against its parents in the `ElementInjector` hierarchy.
2. Against its parents in the [`EnvironmentInjector`](/api/core/EnvironmentInjector) hierarchy.

When a component declares a dependency, Angular tries to satisfy that dependency with its own `ElementInjector`. If the component's injector lacks the provider, it passes the request up to its parent component's `ElementInjector`.

The requests keep forwarding up until Angular finds an injector that can handle the request or runs out of ancestor `ElementInjector` hierarchies.

If Angular doesn't find the provider in any `ElementInjector` hierarchies, it goes back to the element where the request originated and looks in the [`EnvironmentInjector`](/api/core/EnvironmentInjector) hierarchy. If Angular still doesn't find the provider, it throws an error.

If you have registered a provider for the same DI token at different levels, the first one Angular encounters is the one it uses to resolve the dependency. If, for example, a provider is registered locally in the component that needs a service, Angular doesn't look for another provider of the same service.

**HELPFUL:** For [`NgModule`](/api/core/NgModule) based applications, Angular will search the `ModuleInjector` hierarchy if it cannot find a provider in the `ElementInjector` hierarchies.

## [Resolution modifiers](#resolution-modifiers)

Angular's resolution behavior can be modified with `optional`, `self`, `skipSelf` and `host`. Import each of them from `@angular/core` and use each in the [`inject`](/api/core/inject) configuration when you inject your service.

### [Types of modifiers](#types-of-modifiers)

Resolution modifiers fall into three categories:

* What to do if Angular doesn't find what you're looking for, that is `optional`
* Where to start looking, that is `skipSelf`
* Where to stop looking, `host` and `self`

By default, Angular always starts at the current [`Injector`](/api/core/Injector) and keeps searching all the way up. Modifiers allow you to change the starting, or *self*, location and the ending location.

Additionally, you can combine all of the modifiers except:

* `host` and `self`
* `skipSelf` and `self`.

### [`optional`](#optional)

`optional` allows Angular to consider a service you inject to be optional. This way, if it can't be resolved at runtime, Angular resolves the service as `null`, rather than throwing an error. In the following example, the service, `OptionalService`, isn't provided in the service, [`ApplicationConfig`](/api/core/ApplicationConfig), [`@NgModule()`](/api/core/NgModule), or component class, so it isn't available anywhere in the app.

### src/app/optional/optional.ts

```
export class Optional {
  public optional? = inject(OptionalService, {optional: true});
}
```

### [`self`](#self)

Use `self` so that Angular will only look at the `ElementInjector` for the current component or directive.

A good use case for `self` is to inject a service but only if it is available on the current host element. To avoid errors in this situation, combine `self` with `optional`.

For example, in the following `SelfNoData`, notice the injected `LeafService` as a property.

### self-no-data.ts

```
@Component({
  selector: 'app-self-no-data',
  templateUrl: './self-no-data.html',
  styleUrls: ['./self-no-data.css'],
})
export class SelfNoData {
  public leaf = inject(LeafService, {optional: true, self: true});
}
```

In this example, there is a parent provider and injecting the service will return the value, however, injecting the service with `self` and `optional` will return `null` because `self` tells the injector to stop searching in the current host element.

Another example shows the component class with a provider for `FlowerService`. In this case, the injector looks no further than the current `ElementInjector` because it finds the `FlowerService` and returns the tulip 🌷.

### src/app/self/self.ts

```
@Component({
  selector: 'app-self',
  templateUrl: './self.html',
  styleUrls: ['./self.css'],
  providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],
})
export class Self {
  public flower = inject(FlowerService, {self: true});
}
```

### [`skipSelf`](#skipself)

`skipSelf` is the opposite of `self`. With `skipSelf`, Angular starts its search for a service in the parent `ElementInjector`, rather than in the current one. So if the parent `ElementInjector` were using the fern `🌿` value for `emoji`, but you had maple leaf `🍁` in the component's `providers` array, Angular would ignore maple leaf `🍁` and use fern `🌿`.

To see this in code, assume that the following value for `emoji` is what the parent component were using, as in this service:

### leaf.service.ts

```
export class LeafService {
  emoji = '🌿';
}
```

Imagine that in the child component, you had a different value, maple leaf 🍁 but you wanted to use the parent's value instead. This is when you'd use `skipSelf`:

### skipself.ts

```
@Component({
  selector: 'app-skipself',
  templateUrl: './skipself.html',
  styleUrls: ['./skipself.css'],
  // Angular would ignore this LeafService instance
  providers: [{provide: LeafService, useValue: {emoji: '🍁'}}],
})
export class Skipself {
  // Use skipSelf as inject option
  public leaf = inject(LeafService, {skipSelf: true});
}
```

In this case, the value you'd get for `emoji` would be fern `🌿`, not maple leaf `🍁`.

#### [`skipSelf` option with `optional`](#skipself-option-with-optional)

Use the `skipSelf` option with `optional` to prevent an error if the value is `null`.

In the following example, the `Person` service is injected during property initialization. `skipSelf` tells Angular to skip the current injector and `optional` will prevent an error should the `Person` service be `null`.

```
class Person {
  parent = inject(Person, {optional: true, skipSelf: true});
}
```

### [`host`](#host)

`host` lets you designate a component as the last stop in the injector tree when searching for providers.

Even if there is a service instance further up the tree, Angular won't continue looking. Use `host` as follows:

### host.ts

```
@Component({
  selector: 'app-host',
  templateUrl: './host.html',
  styleUrls: ['./host.css'],
  // provide the service
  providers: [{provide: FlowerService, useValue: {emoji: '🌷'}}],
})
export class Host {
  // use host when injecting the service
  flower = inject(FlowerService, {host: true, optional: true});
}
```

Since `Host` has the `host` option, no matter what the parent of `Host` might have as a `flower.emoji` value, the `Host` will use tulip `🌷`.

### [Modifiers with constructor injection](#modifiers-with-constructor-injection)

Similarly as presented before, the behavior of constructor injection can be modified with [`@Optional()`](/api/core/Optional), [`@Self()`](/api/core/Self), [`@SkipSelf()`](/api/core/SkipSelf) and [`@Host()`](/api/core/Host).

Import each of them from `@angular/core` and use each in the component class constructor when you inject your service.

### self-no-data.ts

```
export class SelfNoData {
  constructor(@Self() @Optional() public leaf?: LeafService) {}
}
```

## [Logical structure of the template](#logical-structure-of-the-template)

When you provide services in the component class, services are visible within the `ElementInjector` tree relative to where and how you provide those services.

Understanding the underlying logical structure of the Angular template will give you a foundation for configuring services and in turn control their visibility.

Components are used in your templates, as in the following example:

```
<app-root> <app-child />; </app-root>
```

**HELPFUL:** Usually, you declare the components and their templates in separate files. For the purposes of understanding how the injection system works, it is useful to look at them from the point of view of a combined logical tree. The term *logical* distinguishes it from the render tree, which is your application's DOM tree. To mark the locations of where the component templates are located, this guide uses the `<#VIEW>` pseudo-element, which doesn't actually exist in the render tree and is present for mental model purposes only.

The following is an example of how the `<app-root>` and `<app-child>` view trees are combined into a single logical tree:

```
<app-root>
  <#VIEW>
    <app-child>
     <#VIEW>
       …content goes here…
     </#VIEW>
    </app-child>
  </#VIEW>
</app-root>
```

Understanding the idea of the `<#VIEW>` demarcation is especially significant when you configure services in the component class.

## [Example: Providing services in `@Component()`](#example-providing-services-in-component)

How you provide services using a [`@Component()`](/api/core/Component) (or [`@Directive()`](/api/core/Directive)) decorator determines their visibility. The following sections demonstrate `providers` and `viewProviders` along with ways to modify service visibility with `skipSelf` and `host`.

A component class can provide services in two ways:

| Arrays                       | Details                                        |
| :--------------------------- | :--------------------------------------------- |
| With a `providers` array     | `@Component({ providers: [SomeService] })`     |
| With a `viewProviders` array | `@Component({ viewProviders: [SomeService] })` |

In the examples below, you will see the logical tree of an Angular application. To illustrate how the injector works in the context of templates, the logical tree will represent the HTML structure of the application. For example, the logical tree will show that `<child-component>` is a direct child of `<parent-component>`.

In the logical tree, you will see special attributes: `@Provide`, [`@Inject`](/api/core/Inject), and [`@ApplicationConfig`](/api/core/ApplicationConfig). These aren't real attributes but are here to demonstrate what is going on under the hood.

| Angular service attribute                           | Details                                                                                                              |
| :-------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- |
| `@Inject(Token)=>Value`                             | If `Token` is injected at this location in the logical tree, its value would be `Value`.                             |
| `@Provide(Token=Value)`                             | Indicates that `Token` is provided with `Value` at this location in the logical tree.                                |
| [`@ApplicationConfig`](/api/core/ApplicationConfig) | Demonstrates that a fallback [`EnvironmentInjector`](/api/core/EnvironmentInjector) should be used at this location. |

### [Example app structure](#example-app-structure)

The example application has a `FlowerService` provided in `root` with an `emoji` value of red hibiscus `🌺`.

### flower.service.ts

```
@Service()
export class FlowerService {
  emoji = '🌺';
}
```

Consider an application with only an `App` and a `Child`. The most basic rendered view would look like nested HTML elements such as the following:

```
<app-root>
  <!-- App selector -->
  <app-child> <!-- Child selector --> </app-child>
</app-root>
```

However, behind the scenes, Angular uses a logical view representation as follows when resolving injection requests:

```
<app-root> <!-- App selector -->
  <#VIEW>
    <app-child> <!-- Child selector -->
      <#VIEW>
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>
```

The `<#VIEW>` here represents an instance of a template. Notice that each component has its own `<#VIEW>`.

Knowledge of this structure can inform how you provide and inject your services, and give you complete control of service visibility.

Now, consider that `<app-root>` injects the `FlowerService`:

```
export class App {
  flower = inject(FlowerService);
}
```

Add a binding to the `<app-root>` template to visualize the result:

```
<p>Emoji from FlowerService: {{flower.emoji}}</p>
```

The output in the view would be:

```
Emoji from FlowerService: 🌺
```

In the logical tree, this would be represented as follows:

```
<app-root @ApplicationConfig
        @Inject(FlowerService) flower=>"🌺">
  <#VIEW>
    <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
    <app-child>
      <#VIEW>
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>
```

When `<app-root>` requests the `FlowerService`, it is the injector's job to resolve the `FlowerService` token. The resolution of the token happens in two phases:

1. The injector determines the starting location in the logical tree and an ending location of the search. The injector begins with the starting location and looks for the token at each view level in the logical tree. If the token is found it is returned.

2. If the token is not found, the injector looks for the closest parent [`EnvironmentInjector`](/api/core/EnvironmentInjector) to delegate the request to.

In the example case, the constraints are:

1. Start with `<#VIEW>` belonging to `<app-root>` and end with `<app-root>`.

   * Normally the starting point for search is at the point of injection. However, in this case `<app-root>` is a component. [`@Component`](/api/core/Component)s are special in that they also include their own `viewProviders`, which is why the search starts at `<#VIEW>` belonging to `<app-root>`. This would not be the case for a directive matched at the same location.
   * The ending location happens to be the same as the component itself, because it is the topmost component in this application.

2. The [`EnvironmentInjector`](/api/core/EnvironmentInjector) provided by the [`ApplicationConfig`](/api/core/ApplicationConfig) acts as the fallback injector when the injection token can't be found in the `ElementInjector` hierarchies.

### [Using the `providers` array](#using-the-providers-array)

Now, in the `Child` class, add a provider for `FlowerService` to demonstrate more complex resolution rules in the upcoming sections:

```
@Component({
  selector: 'app-child',
  templateUrl: './child.html',
  styleUrls: ['./child.css'],
  // use the providers array to provide a service
  providers: [{provide: FlowerService, useValue: {emoji: '🌻'}}],
})
export class Child {
  // inject the service
  flower = inject(FlowerService);
}
```

Now that the `FlowerService` is provided in the [`@Component()`](/api/core/Component) decorator, when the `<app-child>` requests the service, the injector has only to look as far as the `ElementInjector` in the `<app-child>`. It won't have to continue the search any further through the injector tree.

The next step is to add a binding to the `Child` template.

```
<p>Emoji from FlowerService: {{flower.emoji}}</p>
```

To render the new values, add `<app-child>` to the bottom of the `App` template so the view also displays the sunflower:

```
Child Component
Emoji from FlowerService: 🌻
```

In the logical tree, this is represented as follows:

```
<app-root @ApplicationConfig
          @Inject(FlowerService) flower=>"🌺">
  <#VIEW>

  <p>Emoji from FlowerService: {{flower.emoji}} (🌺)</p>
  <app-child @Provide(FlowerService="🌻" )
             @Inject(FlowerService)=>"🌻"> <!-- search ends here -->
    <#VIEW> <!-- search starts here -->
    <h2>Child Component</h2>
    <p>Emoji from FlowerService: {{flower.emoji}} (🌻)</p>
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>
```

When `<app-child>` requests the `FlowerService`, the injector begins its search at the `<#VIEW>` belonging to `<app-child>` (`<#VIEW>` is included because it is injected from [`@Component()`](/api/core/Component)) and ends with `<app-child>`. In this case, the `FlowerService` is resolved in the `providers` array with sunflower `🌻` of the `<app-child>`. The injector doesn't have to look any further in the injector tree. It stops as soon as it finds the `FlowerService` and never sees the red hibiscus `🌺`.

### [Using the `viewProviders` array](#using-the-viewproviders-array)

Use the `viewProviders` array as another way to provide services in the [`@Component()`](/api/core/Component) decorator. Using `viewProviders` makes services visible in the `<#VIEW>`.

**HELPFUL:** The steps are the same as using the `providers` array, with the exception of using the `viewProviders` array instead.

For step-by-step instructions, continue with this section. If you can set it up on your own, skip ahead to [Modifying service availability](#visibility-of-provided-tokens).

For demonstration, we are building an `AnimalService` to demonstrate `viewProviders`. First, create an `AnimalService` with an `emoji` property of whale `🐳`:

```
import {Service} from '@angular/core';

@Service()
export class AnimalService {
  emoji = '🐳';
}
```

Following the same pattern as with the `FlowerService`, inject the `AnimalService` in the `App` class:

```
export class App {
  public flower = inject(FlowerService);
  public animal = inject(AnimalService);
}
```

**HELPFUL:** You can leave all the `FlowerService` related code in place as it will allow a comparison with the `AnimalService`.

Add a `viewProviders` array and inject the `AnimalService` in the `<app-child>` class, too, but give `emoji` a different value. Here, it has a value of dog 🐶.

```
@Component({
  selector: 'app-child',
  templateUrl: './child.html',
  styleUrls: ['./child.css'],
  // provide services
  providers: [{provide: FlowerService, useValue: {emoji: '🌻'}}],
  viewProviders: [{provide: AnimalService, useValue: {emoji: '🐶'}}],
})
export class Child {
  // inject services
  flower = inject(FlowerService);
  animal = inject(AnimalService);
}
```

Add bindings to the `Child` and the `App` templates. In the `Child` template, add the following binding:

```
<p>Emoji from AnimalService: {{animal.emoji}}</p>
```

Additionally, add the same to the `App` template:

```
<p>Emoji from AnimalService: {{animal.emoji}}</p>
```

Now you should see both values in the browser:

```
App
Emoji from AnimalService: 🐳

Child Component
Emoji from AnimalService: 🐶
```

The logic tree for this example of `viewProviders` is as follows:

```
<app-root @ApplicationConfig
          @Inject(AnimalService) animal=>"🐳">
  <#VIEW>
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    @Inject(AnimalService=>"🐶")>

    <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
    <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>
```

Just as with the `FlowerService` example, the `AnimalService` is provided in the `<app-child>` [`@Component()`](/api/core/Component) decorator. This means that since the injector first looks in the `ElementInjector` of the component, it finds the `AnimalService` value of dog `🐶`. It doesn't need to continue searching the `ElementInjector` tree, nor does it need to search the `ModuleInjector`.

### [`providers` vs. `viewProviders`](#providers-vs-viewproviders)

The `viewProviders` field is conceptually similar to `providers`, but there is one notable difference. Providers in `viewProviders` are only visible inside the component's own view — content projected into the component via `<ng-content>` cannot see them.

To see the difference between using `providers` and `viewProviders`, add another component to the example and call it `Inspector`. `Inspector` will be a child of the `Child`. In `inspector.ts`, inject the `FlowerService` and `AnimalService` during property initialization:

```
export class Inspector {
  flower = inject(FlowerService);
  animal = inject(AnimalService);
}
```

You do not need a `providers` or `viewProviders` array. Next, in `inspector.html`, add the same markup from previous components:

```
<p>Emoji from FlowerService: {{flower.emoji}}</p>
<p>Emoji from AnimalService: {{animal.emoji}}</p>
```

Remember to add the `Inspector` to the `Child` `imports` array.

```
@Component({
  ...
  imports: [Inspector]
})
```

Next, add the following to `child.html`:

```
...

<div class="container">
  <h3>Content projection</h3>
  <ng-content />
</div>
<h3>Inside the view</h3>

<app-inspector />
```

`<ng-content>` allows you to project content, and `<app-inspector>` inside the `Child` template makes the `Inspector` a child component of `Child`.

Next, add the following to `app.html` to take advantage of content projection.

```
<app-child>
  <app-inspector />
</app-child>
```

The browser now renders the following, omitting the previous examples for brevity:

```
...
Content projection

Emoji from FlowerService: 🌻
Emoji from AnimalService: 🐳

Emoji from FlowerService: 🌻
Emoji from AnimalService: 🐶
```

These four bindings demonstrate the difference between `providers` and `viewProviders`. Remember that the dog emoji `🐶` is declared inside the `<#VIEW>` of `Child` and isn't visible to the projected content. Instead, the projected content sees the whale `🐳`.

You might wonder why the projected `<app-inspector>` can still see `🐳` from `App`'s `viewProviders`. The reason is that Angular DI tracks **where a component was declared**, not where it ends up being rendered. `<app-inspector>` lives in `App`'s template — inside `App`'s `<#VIEW>` — so `App`'s `viewProviders` are fair game. Projecting it into `Child` cuts off access to `Child`'s `viewProviders` (`🐶`), but `App`'s providers (`🐳`) are still reachable up the tree.

However, in the next output section though, the `Inspector` is an actual child component of `Child`, `Inspector` is inside the `<#VIEW>`, so when it asks for the `AnimalService`, it sees the dog `🐶`.

The `AnimalService` in the logical tree would look like this:

```
<app-root @ApplicationConfig
          @Inject(AnimalService) animal=>"🐳">
  <#VIEW>
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    @Inject(AnimalService=>"🐶")>

    <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
    <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>

    <div class="container">
      <h3>Content projection</h3>
      <app-inspector @Inject(AnimalService) animal=>"🐳">
        <p>Emoji from AnimalService: {{animal.emoji}} (🐳)</p>
      </app-inspector>
    </div>

    <app-inspector>
      <#VIEW @Inject(AnimalService) animal=>"🐶">
      <p>Emoji from AnimalService: {{animal.emoji}} (🐶)</p>
    </
    #VIEW>
    </app-inspector>
  </
  #VIEW>
  </app-child>

</#VIEW>
</app-root>
```

The projected `<app-inspector>` gets `🐳` because `🐶` belongs to `Child`'s view and projected content can't reach it. `🐳` is accessible because `<app-inspector>` was declared in `App`'s template, so it can still walk up to `App`'s `viewProviders`.

The `<app-inspector>` that lives directly inside `Child`'s template (not projected) gets `🐶` — it's inside the `<#VIEW>`, so no boundary to cross.

### [Giving projected content access to a view injector](#giving-projected-content-access-to-a-view-injector)

Content projected with `<ng-content>` can't see a component's `viewProviders`, because Angular resolves injection against the injector where the content is declared, not where it renders.

When you specifically want projected content to reach a view-level service — or the projecting component instance itself — accept the content as a template instead of `<ng-content>` and render it with an explicit injector.

Update `Child` to provide `AnimalService` in `viewProviders`, query the projected template, and render it through [`NgTemplateOutlet`](/api/common/NgTemplateOutlet) with `ngTemplateOutletInjector` set to an injector that can see that service:

```
@Component({
  selector: 'app-child',
  viewProviders: [AnimalService],
  imports: [NgTemplateOutlet],
  template: `
    <ng-container [ngTemplateOutlet]="content()" [ngTemplateOutletInjector]="injector" />
  `,
})
export class Child {
  readonly content = contentChild.required(TemplateRef);
  readonly injector = inject(Injector);
}
```

The consumer wraps the projected markup in an `<ng-template>`:

```
<app-child>
  <ng-template>
    <app-inspector />
  </ng-template>
</app-child>
```

`Child` now creates `<app-inspector>` at the outlet using its own injector, so `AnimalService` resolves to the dog `🐶` even though the markup is written in `App`'s template. The cost is a little extra markup on both sides compared with `<ng-content>`.

### [Visibility of provided tokens](#visibility-of-provided-tokens)

Visibility decorators influence where the search for the injection token begins and ends in the logic tree. To do this, place visibility configuration at the point of injection, that is, when invoking `inject()`, rather than at a point of declaration.

To alter where the injector starts looking for `FlowerService`, add `skipSelf` to the `<app-child>` `inject()` invocation where `FlowerService` is injected. This invocation is a property initializer in `<app-child>` as shown in `child.ts`:

```
flower = inject(FlowerService, {skipSelf: true});
```

With `skipSelf`, the `<app-child>` injector doesn't look to itself for the `FlowerService`. Instead, the injector starts looking for the `FlowerService` at the `ElementInjector` of the `<app-root>`, where it finds nothing. Then, it goes back to the `<app-child>` `ModuleInjector` and finds the red hibiscus `🌺` value, which is available because `<app-child>` and `<app-root>` share the same `ModuleInjector`. The UI renders the following:

```
Emoji from FlowerService: 🌺
```

In a logical tree, this same idea might look like this:

```
<app-root @ApplicationConfig
          @Inject(FlowerService) flower=>"🌺">
  <#VIEW>
  <app-child @Provide(FlowerService="🌻" )>
    <#VIEW @Inject(FlowerService, SkipSelf)=>"🌺">

    <!-- With SkipSelf, the injector looks to the next injector up the tree (app-root) -->

  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>
```

Though `<app-child>` provides the sunflower `🌻`, the application renders the red hibiscus `🌺` because `skipSelf` causes the current injector (`app-child`) to skip itself and look to its parent.

If you now add `host` (in addition to the `skipSelf`), the result will be `null`. This is because `host` limits the upper bound of the search to the `app-child` `<#VIEW>`. Here's the idea in the logical tree:

```
<app-root @ApplicationConfig
          @Inject(FlowerService) flower=>"🌺">
  <#VIEW> <!-- end search here with null-->
  <app-child @Provide(FlowerService="🌻" )> <!-- start search here -->
    <#VIEW inject(FlowerService, {skipSelf: true, host: true, optional:true})=>null>
  </
  #VIEW>
  </app-parent>
</#VIEW>
</app-root>
```

Here, the services and their values are the same, but `host` stops the injector from looking any further than the `<#VIEW>` for `FlowerService`, so it doesn't find it and returns `null`.

### [`skipSelf` and `viewProviders`](#skipself-and-viewproviders)

Remember, `<app-child>` provides the `AnimalService` in the `viewProviders` array with the value of dog `🐶`. Because the injector has only to look at the `ElementInjector` of the `<app-child>` for the `AnimalService`, it never sees the whale `🐳`.

As in the `FlowerService` example, if you add `skipSelf` to the `inject()` of `AnimalService`, the injector won't look in the `ElementInjector` of the current `<app-child>` for the `AnimalService`. Instead, the injector will begin at the `<app-root>` `ElementInjector`.

```
@Component({
  selector: 'app-child',
  …
  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '🐶' } },
  ],
})
```

The logical tree looks like this with `skipSelf` in `<app-child>`:

```
<app-root @ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW><!-- search begins here -->
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    @Inject(AnimalService, SkipSelf=>"🐳")>

    <!--Add skipSelf -->

  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>
```

With `skipSelf` in the `<app-child>`, the injector begins its search for the `AnimalService` in the `<app-root>` `ElementInjector` and finds whale 🐳.

### [`host` and `viewProviders`](#host-and-viewproviders)

If you just use `host` for the injection of `AnimalService`, the result is dog `🐶` because the injector finds the `AnimalService` in the `<app-child>` `<#VIEW>` itself. The `Child` configures the `viewProviders` so that the dog emoji is provided as `AnimalService` value. You can also see `host` the `inject()`:

```
@Component({
  selector: 'app-child',
  …
  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '🐶' } },
  ]
})
export class Child {
  animal = inject(AnimalService, { host: true })
}
```

`host: true` causes the injector to look until it encounters the edge of the `<#VIEW>`.

```
<app-root @ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW>
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    inject(AnimalService, {host: true}=>"🐶")> <!-- host stops search here -->
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>
```

Add a `viewProviders` array with a third animal, hedgehog `🦔`, to the `app.ts` [`@Component()`](/api/core/Component) metadata:

```
@Component({
  selector: 'app-root',
  templateUrl: './app.html',
  styleUrls: [ './app.css' ],
  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '🦔' } },
  ],
})
```

Next, add `skipSelf` along with `host` to the `inject()` for the `AnimalService` injection in `child.ts`. Here are `host` and `skipSelf` in the `animal` property initialization:

```
export class Child {
  animal = inject(AnimalService, {host: true, skipSelf: true});
}
```

When `host` and `skipSelf` were applied to the `FlowerService`, which is in the `providers` array, the result was `null` because `skipSelf` starts its search in the `<app-child>` injector, but `host` stops searching at `<#VIEW>` —where there is no `FlowerService` In the logical tree, you can see that the `FlowerService` is visible in `<app-child>`, not its `<#VIEW>`.

However, the `AnimalService`, which is provided in the `App` `viewProviders` array, is visible.

The logical tree representation shows why this is:

```
<app-root @ApplicationConfig
          @Inject(AnimalService=>"🐳")>
  <#VIEW @Provide(AnimalService="🦔")
  @Inject(AnimalService, @Optional)=>"🦔">

  <!-- ^^skipSelf starts here,  host stops here^^ -->
  <app-child>
    <#VIEW @Provide(AnimalService="🐶")
    inject(AnimalService, {skipSelf:true, host: true, optional: true})=>"🦔">
    <!-- Add skipSelf ^^-->
  </
  #VIEW>
  </app-child>
</#VIEW>
</app-root>
```

`skipSelf`, causes the injector to start its search for the `AnimalService` at the `<app-root>`, not the `<app-child>`, where the request originates, and `host` stops the search at the `<app-root>` `<#VIEW>`. Since `AnimalService` is provided by way of the `viewProviders` array, the injector finds hedgehog `🦔` in the `<#VIEW>`.

## [Example: `ElementInjector` use cases](#example-elementinjector-use-cases)

The ability to configure one or more providers at different levels opens up useful possibilities.

### [Scenario: service isolation](#scenario-service-isolation)

Architectural reasons may lead you to restrict access to a service to the application domain where it belongs. For example, consider we build a `VillainsList` that displays a list of villains. It gets those villains from a `VillainsService`.

If you provide `VillainsService` in the root `AppModule`, it will make `VillainsService` visible everywhere in the application. If you later modify the `VillainsService`, you could break something in other components that started depending on this service by accident.

Instead, you should provide the `VillainsService` in the `providers` metadata of the `VillainsList` like this:

```
@Component({
  selector: 'app-villains-list',
  templateUrl: './villains-list.html',
  providers: [VillainsService],
})
export class VillainsList {}
```

By providing `VillainsService` in the `VillainsList` metadata and nowhere else, the service becomes available only in the `VillainsList` and its subcomponent tree.

`VillainsService` is a singleton with respect to `VillainsList` because that is where it is declared. As long as `VillainsList` does not get destroyed it will be the same instance of `VillainsService` but if there are multiple instances of `VillainsList`, then each instance of `VillainsList` will have its own instance of `VillainsService`.

### [Scenario: multiple edit sessions](#scenario-multiple-edit-sessions)

Many applications allow users to work on several open tasks at the same time. For example, in a tax preparation application, the preparer could be working on several tax returns, switching from one to the other throughout the day.

To demonstrate that scenario, imagine a `HeroList` that displays a list of super heroes.

To open a hero's tax return, the preparer clicks on a hero name, which opens a component for editing that return. Each selected hero tax return opens in its own component and multiple returns can be open at the same time.

Each tax return component has the following characteristics:

* Is its own tax return editing session
* Can change a tax return without affecting a return in another component
* Has the ability to save the changes to its tax return or cancel them

Suppose that the `HeroTaxReturnEditor` had logic to manage and restore changes. That would be a straightforward task for a hero tax return. In the real world, with a rich tax return data model, the change management would be tricky. You could delegate that management to a helper service, as this example does.

The `HeroTaxReturnService` caches a single `HeroTaxReturn`, tracks changes to that return, and can save or restore it. It also delegates to the application-wide singleton `HeroService`, which it gets by injection.

```
import {inject, Service} from '@angular/core';
import {HeroTaxReturn} from './hero';
import {HeroesService} from './heroes.service';

@Service({autoProvided: false})
export class HeroTaxReturnService {
  private currentTaxReturn!: HeroTaxReturn;
  private originalTaxReturn!: HeroTaxReturn;

  private heroService = inject(HeroesService);

  set taxReturn(htr: HeroTaxReturn) {
    this.originalTaxReturn = htr;
    this.currentTaxReturn = htr.clone();
  }

  get taxReturn(): HeroTaxReturn {
    return this.currentTaxReturn;
  }

  restoreTaxReturn() {
    this.taxReturn = this.originalTaxReturn;
  }

  saveTaxReturn() {
    this.taxReturn = this.currentTaxReturn;
    this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe();
  }
}
```

Here is the `HeroTaxReturnEditor` that makes use of `HeroTaxReturnService`.

```
import {Component, effect, inject, input, output} from '@angular/core';
import {HeroTaxReturn} from './hero';
import {HeroTaxReturnService} from './hero-tax-return.service';

@Component({
  selector: 'app-hero-tax-return',
  templateUrl: './hero-tax-return.html',
  styleUrls: ['./hero-tax-return.css'],
  providers: [HeroTaxReturnService],
})
export class HeroTaxReturnEditor {
  message = '';

  close = output<void>();

  taxReturn = input.required<HeroTaxReturn>();

  private heroTaxReturnService = inject(HeroTaxReturnService);

  constructor() {
    effect(() => {
      this.heroTaxReturnService.taxReturn = this.taxReturn();
    });
  }

  onCanceled() {
    this.flashMessage('Canceled');
    this.heroTaxReturnService.restoreTaxReturn();
  }

  onClose() {
    this.close.emit();
  }

  onSaved() {
    this.flashMessage('Saved');
    this.heroTaxReturnService.saveTaxReturn();
  }

  flashMessage(msg: string) {
    this.message = msg;
    setTimeout(() => (this.message = ''), 500);
  }
}
```

The *tax-return-to-edit* arrives by way of the `taxReturn` signal input. An [`effect`](/api/core/effect) initializes the component's own instance of the `HeroTaxReturnService` with the incoming return whenever the input changes. The service's getter always returns what the service says is the current state of the hero. The component also asks the service to save and restore this tax return.

This won't work if the service is an application-wide singleton. Every component would share the same service instance, and each component would overwrite the tax return that belonged to another hero.

To prevent this, configure the component-level injector of `HeroTaxReturnEditor` to provide the service, using the `providers` property in the component metadata.

```
providers: [HeroTaxReturnService];
```

The `HeroTaxReturnEditor` has its own provider of the `HeroTaxReturnService`. Recall that every component *instance* has its own injector. Providing the service at the component level ensures that *every* instance of the component gets a private instance of the service. This makes sure that no tax return gets overwritten.

**HELPFUL:** The rest of the scenario code relies on other Angular features and techniques that you can learn about elsewhere in the documentation.

### [Scenario: specialized providers](#scenario-specialized-providers)

Another reason to provide a service again at another level is to substitute a *more specialized* implementation of that service, deeper in the component tree.

For example, consider a `Car` component that includes tire service information and depends on other services to provide more details about the car.

The root injector, marked as (A), uses *generic* providers for details about `CarService` and `EngineService`.

1. `Car` component (A). Component (A) displays tire service data about a car and specifies generic services to provide more information about the car.

2. Child component (B). Component (B) defines its own, *specialized* providers for `CarService` and `EngineService` that have special capabilities suitable for what's going on in component (B).

3. Child component (C) as a child of Component (B). Component (C) defines its own, even *more specialized* provider for `CarService`.

Behind the scenes, each component sets up its own injector with zero, one, or more providers defined for that component itself.

When you resolve an instance of `Car` at the deepest component (C), its injector produces:

* An instance of `Car` resolved by injector (C)
* An `Engine` resolved by injector (B)
* Its `Tires` resolved by the root injector (A).

## [More on dependency injection](#more-on-dependency-injection)

----
url: https://angular.dev/guide/routing/show-routes-with-outlets
----

The [`RouterOutlet`](/api/router/RouterOutlet) directive is a placeholder that marks the location where the router should render the component for the current URL.

```
<app-header />
<!-- Angular inserts your route content here -->
<router-outlet />
<app-footer />
```

```
import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {}
```

In this example, if an application has the following routes defined:

```
import {Routes} from '@angular/router';
import {Home} from './home';
import {Products} from './products';

const routes: Routes = [
  {
    path: '',
    component: Home,
    title: 'Home Page',
  },
  {
    path: 'products',
    component: Products,
    title: 'Our Products',
  },
];
```

When a user visits `/products`, Angular renders the following:

```
<app-header />
<app-products />
<app-footer />
```

If the user goes back to the home page, then Angular renders:

```
<app-header />
<app-home />
<app-footer />
```

When displaying a route, the `<router-outlet>` element remains present in the DOM as a reference point for future navigations. Angular inserts routed content just after the outlet element as a sibling.

```
<!-- Contents of the component's template -->
<app-header />
<router-outlet />
<app-footer />
```

```
<!-- Content rendered on the page when the user visits /admin -->
<app-header />
<router-outlet />
<app-admin-page />
<app-footer />
```

## [Nesting routes with child routes](#nesting-routes-with-child-routes)

As your application grows more complex, you might want to create routes that are relative to a component other than your root component. This enables you to create experiences where only part of the application changes when the URL changes, as opposed to the users feeling like the entire page is refreshed.

These types of nested routes are called child routes. This means you're adding a second `<router-outlet>` to your app, because it is in addition to the `<router-outlet>` in AppComponent.

In this example, the `Settings` component will display the desired panel based on what the user selects. One of the unique things you’ll notice about child routes is that the component often has its own `<nav>` and `<router-outlet>`.

```
<h1>Settings</h1>
<nav>
  <ul>
    <li><a routerLink="profile">Profile</a></li>
    <li><a routerLink="security">Security</a></li>
  </ul>
</nav>
<router-outlet />
```

A child route is like any other route, in that it needs both a `path` and a `component`. The one difference is that you place child routes in a children array within the parent route.

```
const routes: Routes = [
  {
    path: 'settings',
    component: Settings, // this is the component with the <router-outlet> in the template
    children: [
      {
        path: 'profile', // child route path
        component: Profile, // child route component that the router renders
      },
      {
        path: 'security',
        component: Security, // another child route component that the router renders
      },
    ],
  },
];
```

Once both the `routes` and `<router-outlet>` are configured correctly, your application is now using nested routes!

## [Secondary routes with named outlets](#secondary-routes-with-named-outlets)

Pages may have multiple outlets— you can assign a name to each outlet to specify which content belongs to which outlet.

```
<app-header />
<router-outlet />
<router-outlet name="read-more" />
<router-outlet name="additional-actions" />
<app-footer />
```

Each outlet must have a unique name. The name cannot be set or changed dynamically. By default, the name is `'primary'`.

Angular matches the outlet's name to the `outlet` property defined on each route:

```
{
  path: 'user/:id',
  component: UserDetails,
  outlet: 'additional-actions'
}
```

## [Outlet lifecycle events](#outlet-lifecycle-events)

There are four lifecycle events that a router outlet can emit:

| Event        | Description                                                                                                |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| `activate`   | When a new component is instantiated                                                                       |
| `deactivate` | When a component is destroyed                                                                              |
| `attach`     | When the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs the outlet to attach the subtree |
| `detach`     | When the [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) instructs the outlet to detach the subtree |

You can add event listeners with the standard event binding syntax:

```
<router-outlet
  (activate)="onActivate($event)"
  (deactivate)="onDeactivate($event)"
  (attach)="onAttach($event)"
  (detach)="onDetach($event)"
/>
```

Check out the [API docs for RouterOutlet](/api/router/RouterOutlet?tab=api) if you’d like to learn more.

## [Passing contextual data to routed components](#passing-contextual-data-to-routed-components)

Passing contextual data to routed components often requires global state or complicated route configurations. To make this easier, each [`RouterOutlet`](/api/router/RouterOutlet) supports a `routerOutletData` input. Routed components and their children can read this data as a signal using the [`ROUTER_OUTLET_DATA`](/api/router/ROUTER_OUTLET_DATA) injection token, allowing outlet-specific configuration without modifying route definitions.

```
import {Component} from '@angular/core';
import {RouterOutlet} from '@angular/router';

@Component({
  selector: 'app-dashboard',
  imports: [RouterOutlet],
  template: `
    <h2>Dashboard</h2>
    <router-outlet [routerOutletData]="{layout: 'sidebar'}" />
  `,
})
export class Dashboard {}
```

The routed component can inject the provided outlet data using [`ROUTER_OUTLET_DATA`](/api/router/ROUTER_OUTLET_DATA):

```
import {Component, inject} from '@angular/core';
import {ROUTER_OUTLET_DATA} from '@angular/router';

@Component({
  selector: 'app-stats',
  template: `<p>Stats view (layout: {{ outletData().layout }})</p>`,
})
export class Stats {
  outletData = inject(ROUTER_OUTLET_DATA) as Signal<{layout: string}>;
}
```

When Angular activates the `Stats` in that outlet, it receives `{ layout: 'sidebar' }` as injected data.

**NOTE:** When the `routerOutletData` input is unset, the injected signal's value is `undefined` by default.

***

## [Next steps](#next-steps)

Learn how to [navigate to routes](/guide/routing/navigate-to-routes) with Angular Router.

----
url: https://angular.dev/api/core/LOCALE_ID
----

## [API](/api/core/LOCALE_ID#api)

```
    
      const LOCALE_ID: InjectionToken<string>;
    
    
```

## [Description](/api/core/LOCALE_ID#description)

Provide this token to set the locale of your application. It is used for i18n extraction, by i18n pipes (DatePipe, I18nPluralPipe, CurrencyPipe, DecimalPipe and PercentPipe) and by ICU expressions.

See the [i18n guide](guide/i18n/locale-id) for more information.

## [Usage Notes](/api/core/LOCALE_ID#usage-notes)

### [Example](/api/core/LOCALE_ID#example)

In standalone apps:

```
import { LOCALE_ID, ApplicationConfig } from '@angular/core';
import { AppModule } from './app/app.module';

const appConfig: ApplicationConfig = {
  providers: [{provide: LOCALE_ID, useValue: 'en-US' }]
};
```

In module based apps:

```
import { LOCALE_ID } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app/app.module';

platformBrowser().bootstrapModule(AppModule, {
  providers: [{provide: LOCALE_ID, useValue: 'en-US' }]
});
```

Jump to details

----
url: https://angular.dev/errors/NG0602
----

A function that is not allowed to run inside a reactive context was called from within a reactive context.

For example, an [`effect`](/api/core/effect) cannot be scheduled from within a [`computed`](/api/core/computed) or an actively executing effect. Avoid calling functions like [`effect`](/api/core/effect) as part of template expressions, as those execute in their own reactive context.

Computed expressions are expected to be pure. Pure means that expression do not trigger any side effects. Side effects are operations like scheduling [`afterNextRender`](/api/core/afterNextRender)/[`afterEveryRender`](/api/core/afterEveryRender), creating a new [`effect`](/api/core/effect), or subscribing to observables.

Some operations are explicitly banned inside reactive contexts in order to avoid common pitfalls. As an example, using [`afterNextRender`](/api/core/afterNextRender)/[`afterEveryRender`](/api/core/afterEveryRender) inside a [`computed`](/api/core/computed) will schedule new render hooks every time the computed expression evaluates. This is likely not intended and could degrade application performance.

### [Fixing the error](#fixing-the-error)

This error guide is non-exhaustive. It captures a few common scenarios and how to address the error.

#### [`afterNextRender`/`afterEveryRender`](#afternextrender-aftereveryrender)

Move the call for [`afterNextRender`](/api/core/afterNextRender)/[`afterEveryRender`](/api/core/afterEveryRender) outside of the reactive context.

A good place to schedule the after render hook is in the component's class constructor. Alternatively, use [`untracked`](/api/core/untracked) to leave the reactive context and explicitly opt-out of this error.

#### [`effect`](#effect)

Move the call for [`effect`](/api/core/effect) outside of the reactive context.

A good place to schedule an effect is in a [`@Component`](/api/core/Component)'s class constructor.

#### [`toSignal`](#tosignal)

Move the call for [`toSignal`](/api/core/rxjs-interop/toSignal) outside of the reactive context.

```
result = computed(() => {
  const dataSignal = toSignal(dataObservable$);
  return doSomething(dataSignal());
});
```

can be refactored into:

```
dataSignal = toSignal(dataObservable$);
result = computed(() => doSomething(dataSignal()));
```

Alternatively, if this is not possible, consider manually subscribing to the observable.

As a last resort, use [`untracked`](/api/core/untracked) to leave the reactive context. Be careful as leaving the reactive context can result in signal reads to be ignored inside [`untracked`](/api/core/untracked).

### [Debugging the error](#debugging-the-error)

The error message mentions the function that was unexpectedly called. Look for this function call in your application code.

Alternatively, the stack trace in your browser will show where the function was invoked and where it's located.

----
url: https://angular.dev/errors/NG2003
----

There is no injection token for a constructor parameter at compile time. [InjectionTokens](api/core/InjectionToken) are tokens that can be used in a Dependency Injection Provider.

## [Debugging the error](#debugging-the-error)

Look at the parameter that throws the error, and all uses of the class. This error is commonly thrown when a constructor defines parameters with primitive types such as `string`, `number`, `boolean`, and `Object`.

Use the [`@Injectable`](/api/core/Injectable) method or [`@Inject`](/api/core/Inject) decorator from `@angular/core` to ensure that the type you are injecting is reified (has a runtime representation). Make sure to add a provider to this decorator so that you do not throw [NG0201: No Provider Found](errors/NG0201).

----
url: https://angular.dev/tutorials/first-app/04-interfaces
----

[](/tutorials/first-app/03-HousingLocation)[](/tutorials/first-app/05-inputs)

[](/tutorials/first-app/03-HousingLocation)[](/tutorials/first-app/05-inputs)

This tutorial lesson demonstrates how to create an interface and include it in a component of your app.

[](https://www.youtube.com/watch?v=eM3zi_n7lNs\&autoplay=1)

## [What you'll learn](#what-youll-learn)

* Your app has a new interface that it can use as a data type.
* Your app has an instance of the new interface with sample data.

## [Conceptual preview of interfaces](#conceptual-preview-of-interfaces)

[Interfaces](https://www.typescriptlang.org/docs/handbook/interfaces.html) are custom data types for your app.

Angular uses TypeScript to take advantage of working in a strongly typed programming environment. Strong type checking reduces the likelihood of one element in your app sending incorrectly formatted data to another. Such type-mismatch errors are caught by the TypeScript compiler and many such errors can also be caught in your IDE.

In this lesson, you'll create an interface to define properties that represent data about a single housing location.

1. ### [Create a new Angular interface](#create-a-new-angular-interface)

   This step creates a new interface in your app.

   In the **Terminal** pane of your IDE:

   1. In your project directory, navigate to the `first-app` directory.

   2. In the `first-app` directory, run this command to create the new interface.

      ```
      ng generate interface housinglocation
      ```

   3. Run `ng serve` to build the app and serve it to `http://localhost:4200`.

   4. In a browser, open `http://localhost:4200` to see your app.

   5. Confirm that the app builds without error. Correct any errors before you continue to the next step.

2. ### [Add properties to the new interface](#add-properties-to-the-new-interface)

   This step adds the properties to the interface that your app needs to represent a housing location.

   1. In the **Terminal** pane of your IDE, start the `ng serve` command, if it isn't already running, to build the app and serve it to `http://localhost:4200`.

   2. In the **Edit** pane of your IDE, open the `src/app/housinglocation.ts` file.

   3. In `housinglocation.ts`, replace the default content with the following code to make your new interface to match this example.

      ### Update src/app/housinglocation.ts to match this code

      ```
      export interface HousingLocationInfo {
        id: number;
        name: string;
        city: string;
        state: string;
        photo: string;
        availableUnits: number;
        wifi: boolean;
        laundry: boolean;
      }
      ```

   4. Save your changes and confirm the app does not display any errors. Correct any errors before you continue to the next step.

   At this point, you've defined an interface that represents data about a housing location including an `id`, `name`, and location information.

3. ### [Create a test house for your app](#create-a-test-house-for-your-app)

   You have an interface, but you aren't using it yet.

   In this step, you create an instance of the interface and assign some sample data to it. You won't see this sample data appear in your app yet. There are a few more lessons to complete before that happens.

   1. In the **Terminal** pane of your IDE, run the `ng serve` command, if it isn't already running, to build the app and serve your app to `http://localhost:4200`.

   2. In the **Edit** pane of your IDE, open `src/app/home/home.ts`.

   3. In `src/app/home/home.ts`, add this import statement after the existing `import` statements so that `Home` can use the new interface.

      ### Import Home in src/app/home/home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        housingLocation: HousingLocationInfo = {
          id: 9999,
          name: 'Test Home',
          city: 'Test city',
          state: 'ST',
          photo: `${this.baseUrl}/example-house.jpg`,
          availableUnits: 99,
          wifi: true,
          laundry: false,
        };
      }
      ```

   4. In `src/app/home/home.ts`, replace the empty `export class Home {}` definition with this code to create a single instance of the new interface in the component.

      ### Add sample data to src/app/home/home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        housingLocation: HousingLocationInfo = {
          id: 9999,
          name: 'Test Home',
          city: 'Test city',
          state: 'ST',
          photo: `${this.baseUrl}/example-house.jpg`,
          availableUnits: 99,
          wifi: true,
          laundry: false,
        };
      }
      ```

   5. Confirm that your `home.ts` file matches this example.

      ### src/app/home/home.ts

      ```
      import {Component} from '@angular/core';
      import {HousingLocation} from '../housing-location/housing-location';
      import {HousingLocationInfo} from '../housinglocation';

      @Component({
        selector: 'app-home',
        imports: [HousingLocation],
        template: `
          <section>
            <form>
              <input type="text" placeholder="Filter by city" />
              <button class="primary" type="button">Search</button>
            </form>
          </section>
          <section class="results">
            <app-housing-location />
          </section>
        `,
        styleUrls: ['./home.css'],
      })
      export class Home {
        readonly baseUrl = 'https://angular.dev/assets/images/tutorials/common';

        housingLocation: HousingLocationInfo = {
          id: 9999,
          name: 'Test Home',
          city: 'Test city',
          state: 'ST',
          photo: `${this.baseUrl}/example-house.jpg`,
          availableUnits: 99,
          wifi: true,
          laundry: false,
        };
      }
      ```

      By adding the `housingLocation` property of type `HousingLocation` to the `Home` class, we're able to confirm that the data matches the description of the interface. If the data didn't satisfy the description of the interface, the IDE has enough information to give us helpful errors.

   6. Save your changes and confirm the app does not have any errors. Open the browser and confirm that your application still displays the message "housing-location works!"

   7. Correct any errors before you continue to the next step.

**SUMMARY:** In this lesson, you created an interface that created a new data type for your app. This new data type makes it possible for you to specify where `HousingLocation` data is required. This new data type also makes it possible for your IDE and the TypeScript compiler can ensure that `HousingLocation` data is used where it's required.

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/errors/NG0302
----

[](https://www.youtube.com/watch?v=maI2u6Sxk9M\&autoplay=1)

Angular can't find a pipe with this name.

The [pipe](guide/templates/pipes) referenced in the template has not been named or declared properly.

To use the pipe:

* Ensure the name used in a template matches the name defined in the pipe decorator.
* Add the pipe to your component's `imports` array or, if the pipe sets `standalone: false`, add the [`NgModule`](/api/core/NgModule) to which the pipe belongs.

## [Debugging the error](#debugging-the-error)

Use the pipe name to trace where the pipe is declared and used.

To resolve this error:

* If the pipe is local to the [`NgModule`](/api/core/NgModule), give it a unique name in the pipe's decorator and declared it in the [`NgModule`](/api/core/NgModule).
* If the pipe is standalone or is declared in another [`NgModule`](/api/core/NgModule), add it to the `imports` field of the standalone component or the current [`NgModule`](/api/core/NgModule).

If you recently added an import or declaration, you may need to restart your server to see these changes.

----
url: https://angular.dev/api/core/ModuleWithProviders
----

```
    
      interface ModuleWithProviders<T> {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/testing/services
----

Services typically contain your application's business logic that components rely on. Testing services verifies that the logic works correctly in isolation, independent of any component or template.

This guide uses [Vitest](https://vitest.dev/), which Angular CLI projects include by default. For more on testing setup, see the [testing overview guide](guide/testing#set-up-for-testing).

## [Testing a service](#testing-a-service)

Consider a `Calculator` service that performs basic arithmetic:

### calculator.ts

```
import {Service} from '@angular/core';

@Service()
export class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }

  subtract(a: number, b: number): number {
    return a - b;
  }
}
```

To test this service, configure a [`TestBed`](/api/core/testing/TestBed), which is Angular's testing utility for creating an isolated testing environment for each test. It sets up dependency injection and lets you retrieve service instances — simulating how Angular wires things together in a real application.

### calculator.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, expect, it} from 'vitest';
import {Calculator} from './calculator';

describe('Calculator', () => {
  let service: Calculator;

  beforeEach(() => {
    // Injects the Calculator service which is available to Angular
    // because the service uses `@Service`
    service = TestBed.inject(Calculator);
  });

  it('adds two numbers', () => {
    expect(service.add(1, 2)).toBe(3);
  });

  it('subtracts two numbers', () => {
    expect(service.subtract(5, 3)).toBe(2);
  });
});
```

In the example above, the `beforeEach` block injects a fresh instance of the service before every test. This ensures each test runs in isolation with no leaked state from previous tests.

## [Testing services with dependencies](#testing-services-with-dependencies)

Most services depend on other services to run properly. By default, [`TestBed`](/api/core/testing/TestBed) provides the real implementations of these dependencies, which means your tests exercise the actual code paths your application uses. Sometimes, however, a dependency may be complex, slow, or unpredictable. In those cases, you can substitute it with a controlled replacement.

Consider an `OrderTotal` service that relies on a `TaxCalculator` to compute the final price of an order:

### tax-calculator.ts

```
import {Service} from '@angular/core';

@Service()
export class TaxCalculator {
  calculate(subtotal: number): number {
    return subtotal * 0.05;
  }
}
```

### order-total.ts

```
import {inject, Service} from '@angular/core';
import {TaxCalculator} from './tax-calculator';

@Service()
export class OrderTotal {
  private taxCalculator = inject(TaxCalculator);

  total(subtotal: number): number {
    return subtotal + this.taxCalculator.calculate(subtotal);
  }
}
```

In this example, `OrderTotal` uses `inject()` to request `TaxCalculator` from Angular's dependency injection system. By default, [`TestBed`](/api/core/testing/TestBed) provides the real `TaxCalculator` which is perfect for simple calculations like this. However, if `TaxCalculator` involved complex logic, network requests, or unpredictable results, you might want to substitute it with a controlled replacement.

### [Replacing a dependency with a stub](#replacing-a-dependency-with-a-stub)

A stub is a way to replace a dependency or method with one that returns predictable values, which can make test results easier to verify.

To test `OrderTotal` without relying on the real `TaxCalculator`, you can provide a stub in the [`TestBed`](/api/core/testing/TestBed) configuration.

### order-total.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {beforeEach, describe, expect, it, vi, type Mocked} from 'vitest';
import {OrderTotal} from './order-total';
import {TaxCalculator} from './tax-calculator';

// Vitest's `Mocked` utility type ensures the stub is type-safe,
// while `vi.fn()` creates a mock function for each method
const taxCalculatorStub: Mocked<TaxCalculator> = {
  calculate: vi.fn(),
};

describe('OrderTotal', () => {
  let service: OrderTotal;

  beforeEach(() => {
    // `mockReturnValue` sets a controlled return value for the stub
    taxCalculatorStub.calculate.mockReturnValue(5);

    TestBed.configureTestingModule({
      // The `providers` array accepts a provider object where `provide`
      // specifies the dependency to replace and `useValue` defines the stub
      providers: [{provide: TaxCalculator, useValue: taxCalculatorStub}],
    });
    service = TestBed.inject(OrderTotal);
  });

  it('adds tax to the subtotal', () => {
    expect(service.total(100)).toBe(105);
  });
});
```

With this stub, whenever `OrderTotal` requests `TaxCalculator`, the [`TestBed`](/api/core/testing/TestBed) knows to use the `taxCalculatorStub` instead. Because the stub always returns 5, the test verifies that `OrderTotal` correctly adds the tax value to the subtotal regardless of whether the tax rate changes in `TaxCalculator`.

### [Verifying interactions with spies](#verifying-interactions-with-spies)

A stub controls what a dependency returns, but sometimes you also need to verify that a service called its dependency with the correct arguments. This can be accomplished with spies, which track how a function is called. With Vitest, this functionality is built into `vi.fn()` and lets you assert on interactions between services.

### order-total.spec.ts

```
import {TestBed} from '@angular/core/testing';
import {afterEach, beforeEach, describe, expect, it, vi, type Mocked} from 'vitest';
import {OrderTotal} from './order-total';
import {TaxCalculator} from './tax-calculator';

const taxCalculatorStub: Mocked<TaxCalculator> = {
  calculate: vi.fn(),
};

describe('OrderTotal', () => {
  let service: OrderTotal;

  beforeEach(() => {
    taxCalculatorStub.calculate.mockReturnValue(5);

    TestBed.configureTestingModule({
      providers: [{provide: TaxCalculator, useValue: taxCalculatorStub}],
    });
    service = TestBed.inject(OrderTotal);
  });

  afterEach(() => {
    taxCalculatorStub.calculate.mockClear();
  });

  it('adds tax to the subtotal', () => {
    expect(service.total(100)).toBe(105);
  });

  // Verify the interaction with a spy
  it('calls the tax calculator', () => {
    service.total(100);
    expect(taxCalculatorStub.calculate).toHaveBeenCalledExactlyOnceWith(100);
  });
});
```

The new test verifies that `OrderTotal` called `TaxCalculator.calculate` when computing the total. This is useful when verifying that the interaction between services happened correctly.

## [Testing HTTP services](#testing-http-services)

Many services use Angular's [`HttpClient`](/api/common/http/HttpClient) to fetch data from a server. Angular provides dedicated testing utilities for [`HttpClient`](/api/common/http/HttpClient) that let you control HTTP responses without making real network requests.

For details on testing services that use [`HttpClient`](/api/common/http/HttpClient), see the [HTTP testing guide](guide/http/testing).

----
url: https://angular.dev/api/common/LocationStrategy
----

```
    
      abstract class LocationStrategy {}
    
    
```

@paramincludeHash`boolean | undefined`

@returns`string`

@paraminternal`string`

@returns`string`

@returns`unknown`

@paramrelativePosition`number`

@returns`void`

@paramfn`LocationChangeListener`

@returns`void`

@returns`string`

## [Description](#description)

Enables the [`Location`](/api/common/Location) service to read route state from the browser's URL. Angular provides two strategies: [`HashLocationStrategy`](/api/common/HashLocationStrategy) and [`PathLocationStrategy`](/api/common/PathLocationStrategy).

Applications should use the `Router` or [`Location`](/api/common/Location) services to interact with application route state.

For instance, [`HashLocationStrategy`](/api/common/HashLocationStrategy) produces URLs like `http://example.com/#/foo`, and [`PathLocationStrategy`](/api/common/PathLocationStrategy) produces `http://example.com/foo` as an equivalent URL.

See these two classes for more.

Jump to details

----
url: https://angular.dev/api/forms/RangeValueAccessor
----

```
    
      class RangeValueAccessor extends BuiltInControlValueAccessor implements ControlValueAccessor {}
    
    
```

## [Description](#description)

The [`ControlValueAccessor`](/api/forms/ControlValueAccessor) for writing a range value and listening to range input changes. The value accessor is used by the [`FormControlDirective`](/api/forms/FormControlDirective), [`FormControlName`](/api/forms/FormControlName), and [`NgModel`](/api/forms/NgModel) directives.

## [Exported by](#exported-by)

* `ReactiveFormsModule`
* `FormsModule`

## [Usage Notes](#usage-notes)

### [Using a range input with a reactive form](#using-a-range-input-with-a-reactive-form)

The following example shows how to use a range input with a reactive form.

```
const ageControl = new FormControl();
```

```
<input type="range" [formControl]="ageControl">
```

Jump to details

----
url: https://angular.dev/api/platform-browser/MetaDefinition
----

```
    
      type MetaDefinition = {  charset?: string;  content?: string;  httpEquiv?: string;  id?: string;  itemprop?: string;  name?: string;  property?: string;  scheme?: string;  url?: string;} & {  // TODO(IgorMinar): this type looks wrong  [prop: string]: string;}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/DeprecatedGuard
----

```
    
      type DeprecatedGuard = ProviderToken<any> | string
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG0910
----

You see this error when Angular detects an attribute binding or a property binding on an `<iframe>` element using the following property names:

* sandbox
* allow
* allowFullscreen
* referrerPolicy
* csp
* fetchPriority
* credentialless

The mentioned attributes affect the security model setup for `<iframe>`s and it's important to apply them before setting the `src` or `srcdoc` attributes. To enforce that, Angular requires these attributes to be set on `<iframe>`s as static attributes, so the values are set at the element creation time and they remain the same throughout the lifetime of an `<iframe>` instance.

The error is thrown when a property binding with one of the mentioned attribute names is used:

```
<iframe [sandbox]="'allow-scripts'" src="..."></iframe>
```

or when it's an attribute bindings:

```
<iframe [attr.sandbox]="'allow-scripts'" src="..."></iframe>
```

Also, the error is thrown when a similar pattern is used in Directive's host bindings:

```
@Directive({
  selector: 'iframe',
  host: {
    '[sandbox]': `'allow-scripts'`,
    '[attr.sandbox]': `'allow-scripts'`,
  },
})
class IframeDirective {}
```

## [Debugging the error](#debugging-the-error)

The error message includes the name of the component with the template where an `<iframe>` element with unsafe bindings is located.

The recommended solution is to use the mentioned attributes as static ones, for example:

```
<iframe sandbox="allow-scripts" src="..."></iframe>
```

If you need to have different values for these attributes (depending on various conditions), you can use the `@if` or `@switch` control flow blocks to conditionally render the appropriate `<iframe>` element:

```
@if (someConditionA) {
  <iframe sandbox="allow-scripts" src="..."></iframe>
} @else if (someConditionB) {
  <iframe sandbox="allow-forms" src="..."></iframe>
} @else {
  <iframe sandbox="allow-popups" src="..."></iframe>
}
```

----
url: https://angular.dev/api/core/ExistingSansProvider
----

```
    
      interface ExistingSansProvider {}
    
    
```

Existing `token` to return. (Equivalent to `injector.get(useExisting)`)

Jump to details

----
url: https://angular.dev/api/animations/state
----

```
function state(
  name: string,
  styles: AnimationStyleMetadata,
  options?: { params: { [name: string]: any } } | undefined,
): AnimationStateMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramname`string`

One or more names for the defined state in a comma-separated string. The following reserved state names can be supplied to define a style for specific use cases:

* `void` You can associate styles with this name to be used when the element is detached from the application. For example, when an `ngIf` evaluates to false, the state of the associated element is void.
* `*` (asterisk) Indicates the default state. You can associate styles with this name to be used as the fallback when the state that is being animated is not declared within the trigger.

@paramstyles`AnimationStyleMetadata`

A set of CSS styles associated with this state, created using the [`style()`](/api/animations/style) function. This set of styles persists on the element once the state has been reached.

@paramoptions`{ params: { [name: string]: any; }; } | undefined`

Parameters that can be passed to the state when it is invoked. 0 or more key-value pairs.

@returns`AnimationStateMetadata`

## [Usage Notes](#usage-notes)

Use the [`trigger()`](/api/animations/trigger) function to register states to an animation trigger. Use the [`transition()`](/api/animations/transition) function to animate between states. When a state is active within a component, its associated styles persist on the element, even when the animation ends.

Jump to details

----
url: https://angular.dev/api/cdk/drag-drop/DragRefConfig
----

```
    
      interface DragRefConfig {}
    
    
```

Minimum amount of pixels that the user should drag, before the CDK initiates a drag sequence.

Amount the pixels the user should drag before the CDK considers them to have changed the drag direction.

`z-index` for the absolutely-positioned elements that are created by the drag item.

Ref that the current drag item is nested in.

Jump to details

----
url: https://angular.dev/api/core/VERSION
----

```
    
      const VERSION: Version;
    
    
```

Jump to details

----
url: https://angular.dev/api/router/UrlSerializer
----

```
    
      abstract class UrlSerializer {}
    
    
```

Parse a url into a [`UrlTree`](/api/router/UrlTree)

@paramurl`string`

@returns`UrlTree`

Converts a [`UrlTree`](/api/router/UrlTree) into a url

@paramtree`UrlTree`

@returns`string`

## [Description](#description)

Serializes and deserializes a URL string into a URL tree.

The url serialization strategy is customizable. You can make all URLs case insensitive by providing a custom UrlSerializer.

See [`DefaultUrlSerializer`](/api/router/DefaultUrlSerializer) for an example of a URL serializer.

Jump to details

----
url: https://angular.dev/api/ssr/RenderMode
----

```
    
      enum RenderMode {}
    
    
```

Server-Side Rendering (SSR) mode, where content is rendered on the server for each request.

Client-Side Rendering (CSR) mode, where content is rendered on the client side in the browser.

Static Site Generation (SSG) mode, where content is pre-rendered at build time and served as static files.

Jump to details

----
url: https://angular.dev/api/common/http/HttpResponseBase
----

```
    
      abstract class HttpResponseBase {}
    
    
```

Super-constructor for all responses.

The single parameter accepted is an initialization hash. Any properties of the response passed there will override the default values.

@paraminit`{ headers?: HttpHeaders | undefined; status?: number | undefined; statusText?: string | undefined; url?: string | undefined; redirected?: boolean | undefined; responseType?: ResponseType | undefined; }`

@paramdefaultStatus`number`

@paramdefaultStatusText`string`

@returns`HttpResponseBase`


Whether the status code falls in the 2xx range.

Jump to details

----
url: https://angular.dev/api/router/Params
----

```
    
      type Params = {  [key: string]: any;}
    
    
```

Jump to details

----
url: https://angular.dev/cli/config
----

```
ng config [json-path] [value] [options]
```

A workspace has a single CLI configuration file, `angular.json`, at the top level. The `projects` object contains a configuration object for each project in the workspace.

You can edit the configuration directly in a code editor, or indirectly on the command line using this command.

The configurable property names match command option names, except that in the configuration file, all names must use camelCase, while on the command line options can be given dash-case.

For further details, see [Workspace Configuration](reference/configs/workspace-config).

For configuration of CLI usage analytics, see [ng analytics](cli/analytics).

## [Arguments](/cli/config#arguments)

`json-path`

The configuration key to set or query, in JSON path format. For example: "a\[3].foo.bar\[2]". If no new value is provided, returns the current value of this key.

Value Type`string`

`value`

If provided, a new value for the given configuration key.

Value Type`string`

## [Options](/cli/config#options)

`global`

Alias`g`

Access the global configuration in the caller's home directory.

Value Type`boolean`Default`false`

`help`

Shows a help message for this command in the console.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/api/aria/listbox/LISTBOX
----

```
    
      const LISTBOX: InjectionToken<Listbox<any>>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/NG_VALIDATORS
----

```
    
      const NG_VALIDATORS: InjectionToken<readonly (Function | Validator)[]>;
    
    
```

## [Usage Notes](#usage-notes)

### [Providing a custom validator](#providing-a-custom-validator)

The following example registers a custom validator directive. Adding the validator to the existing collection of validators requires the `multi: true` option.

```
@Directive({
  selector: '[customValidator]',
  providers: [{provide: NG_VALIDATORS, useExisting: forwardRef(() => CustomValidatorDirective), multi: true}]
})
class CustomValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors | null {
    return { 'custom': true };
  }
}
```

Jump to details

----
url: https://angular.dev/api/forms/signals/ValidationResult
----

```
    
      type ValidationResult<E extends ValidationError = ValidationError> = ValidationSuccess | OneOrMany<E>
    
    
```

## [Description](#description)

A validation result where all errors explicitly define their target field.

The result may be one of the following:

1. A [`ValidationSuccess`](/api/forms/signals/ValidationSuccess) to indicate no errors.
2. A [`ValidationError`](/api/forms/signals/ValidationError) with a field to indicate an error on the target field.
3. A list of [`ValidationError`](/api/forms/signals/ValidationError) with fields to indicate multiple errors.

Jump to details

----
url: https://angular.dev/api/aria/toolbar/ToolbarWidget
----

```
    
      class ToolbarWidget implements OnInit ,OnDestroy {}
    
    
```

A reference to the host element.

A unique identifier for the widget.

Whether the widget is disabled.

Whether the widget is 'hard' disabled, which is different from `aria-disabled`. A hard disabled widget cannot receive focus.

Whether the widget is currently active (focused).

@returns`void`

@returns`void`

## [Description](#description)

A widget within a toolbar.

The `ngToolbarWidget` directive should be applied to any native HTML element that acts as an interactive widget within an `ngToolbar` or `ngToolbarWidgetGroup`. It enables keyboard navigation within the toolbar.

```
<button ngToolbarWidget [disabled]="isDisabled">
  Perform Action
</button>
```

Jump to details

----
url: https://angular.dev/api/common/http/HttpParameterCodec
----

```
    
      interface HttpParameterCodec {}
    
    
```

@paramkey`string`

@returns`string`

@paramvalue`string`

@returns`string`

@paramkey`string`

@returns`string`

@paramvalue`string`

@returns`string`

## [Description](#description)

A codec for encoding and decoding parameters in URLs.

Used by [`HttpParams`](/api/common/http/HttpParams).

Jump to details

----
url: https://angular.dev/api/core/OnChanges
----

```
    
      interface OnChanges {}
    
    
```

A callback method that is invoked immediately after the default change detector has checked data-bound properties if at least one has changed, and before the view and content children are checked.

@paramchanges`{ [propName: string]: SimpleChange<any>; }`

The changed properties.

@returns`void`

## [Usage Notes](#usage-notes)

The following snippet shows how a component can implement this interface to define an on-changes handler for an input property. While you should prefer [`computed`](/api/core/computed) and [`effect`](/api/core/effect) when working with signal-based inputs, the `ngOnChanges` method does include value changes for signal-based inputs.

```
@Component({ ... })
export class UserProfile implements OnChanges {
  userId = input<number>(0);
  ngOnChanges(changes: SimpleChanges<UserProfile>) {
    // changes.userId contains the old and new value.
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/isDevMode
----

```
function isDevMode(): boolean;
```

@returns`boolean`

## [Description](#description)

Returns whether Angular is in development mode.

By default, this is true, unless [`enableProdMode`](/api/core/enableProdMode) is invoked prior to calling this method or the application is built using the Angular CLI with the `optimization` option.

Jump to details

----
url: https://angular.dev/api/forms/signals/TreeValidator
----

```
    
      type TreeValidator<TValue, TPathKind extends PathKind = PathKind.Root> = LogicFn<  TValue,  TreeValidationResult,  TPathKind>
    
    
```

Jump to details

----
url: https://angular.dev/guide/routing/route-guards
----

**CRITICAL:** Never rely on client-side guards as the sole source of access control. All JavaScript that runs in a web browser can be modified by the user running the browser. Always enforce user authorization server-side, in addition to any client-side guards.

Route guards are functions that control whether a user can navigate to or leave a particular route. They are like checkpoints that manage whether a user can access specific routes. Common examples of using route guards include authentication and access control.

## [Creating a route guard](#creating-a-route-guard)

You can generate a route guard using the Angular CLI:

```
ng generate guard CUSTOM_NAME
```

This will prompt you to select which [type of route guard](#types-of-route-guards) to use and then create the corresponding `CUSTOM_NAME-guard.ts` file.

**TIP:** You can also create a route guard manually by creating a separate TypeScript file in your Angular project. Developers typically add a suffix of `-guard.ts` in the filename to distinguish it from other files.

## [Route guard return types](#route-guard-return-types)

All route guards share the same possible return types. This gives you flexibility in how you control navigation:

| Return types                                                                         | Description                                                                                               |
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `boolean`                                                                            | `true` allows navigation, `false` blocks it (see note for [`CanMatch`](/api/router/CanMatch) route guard) |
| [`UrlTree`](/api/router/UrlTree) or [`RedirectCommand`](/api/router/RedirectCommand) | Redirects to another route instead of blocking                                                            |
| `Promise<T>` or `Observable<T>`                                                      | Router uses the first emitted value and then unsubscribes                                                 |

**NOTE:** [`CanMatch`](/api/router/CanMatch) behaves differently— when it returns `false`, Angular tries other matching routes instead of completely blocking navigation.

## [Types of route guards](#types-of-route-guards)

Angular provides four types of route guards, each serving different purposes:

All the guards have access to [services provided at the route level](guide/di/defining-dependency-providers#route-providers) as well as route-specific information via the `route` argument.

### [CanActivate](#canactivate)

The [`CanActivate`](/api/router/CanActivate) guard determines whether a user can access a route. It is most commonly used for authentication and authorization.

It has access to the following default arguments:

* `route`: [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) - Contains information about the route being activated
* `state`: [`RouterStateSnapshot`](/api/router/RouterStateSnapshot) - Contains the router's current state

It can return the [standard return guard types](#route-guard-return-types).

```
export const authGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const authService = inject(AuthService);
  return authService.isAuthenticated();
};
```

**TIP:** If you need to redirect the user, return a [`URLTree`](api/router/UrlTree) or [`RedirectCommand`](api/router/RedirectCommand). Do **not** return `false` and then programmatically `navigate` the user.

For more information, check out the [API docs for CanActivateFn](api/router/CanActivateFn).

### [CanActivateChild](#canactivatechild)

The [`CanActivateChild`](/api/router/CanActivateChild) guard determines whether a user can access child routes of a particular parent route. This is useful when you want to protect an entire section of nested routes. In other words, `canActivateChild` runs for *all* children. If there is a child component with another child component underneath of it, `canActivateChild` will run once for both components.

It has access to the following default arguments:

* `childRoute`: [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) - Contains information about the "future" snapshot (i.e., state the router is attempting to navigate to) of the child route being activated
* `state`: [`RouterStateSnapshot`](/api/router/RouterStateSnapshot) - Contains the router's current state

It can return the [standard return guard types](#route-guard-return-types).

```
export const adminChildGuard: CanActivateChildFn = (
  childRoute: ActivatedRouteSnapshot,
  state: RouterStateSnapshot,
) => {
  const authService = inject(AuthService);
  return authService.hasRole('admin');
};
```

For more information, check out the [API docs for CanActivateChildFn](api/router/CanActivateChildFn).

### [CanDeactivate](#candeactivate)

The [`CanDeactivate`](/api/router/CanDeactivate) guard determines whether a user can leave a route. A common scenario is preventing navigation away from unsaved forms.

It has access to the following default arguments:

* `component`: `T` - The component instance being deactivated
* `currentRoute`: [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) - Contains information about the current route
* `currentState`: [`RouterStateSnapshot`](/api/router/RouterStateSnapshot) - Contains the current router state
* `nextState`: [`RouterStateSnapshot`](/api/router/RouterStateSnapshot) - Contains the next router state being navigated to

It can return the [standard return guard types](#route-guard-return-types).

```
export const unsavedChangesGuard: CanDeactivateFn<Form> = (
  component: Form,
  currentRoute: ActivatedRouteSnapshot,
  currentState: RouterStateSnapshot,
  nextState: RouterStateSnapshot,
) => {
  return component.hasUnsavedChanges()
    ? confirm('You have unsaved changes. Are you sure you want to leave?')
    : true;
};
```

For more information, check out the [API docs for CanDeactivateFn](api/router/CanDeactivateFn).

### [CanMatch](#canmatch)

The [`CanMatch`](/api/router/CanMatch) guard determines whether a route can be matched during path matching. Unlike other guards, rejection falls through to try other matching routes instead of blocking navigation entirely. This can be useful for feature flags, A/B testing, or conditional route loading.

It has access to the following default arguments:

* `route`: [`Route`](/api/router/Route) - The route configuration being evaluated
* `segments`: `UrlSegment[]` - The URL segments that have not been consumed by previous parent route evaluations
* `currentSnapshot: PartialMatchRouteSnapshot` - The current route snapshot up to this point in the matching process

It can return the [standard return guard types](#route-guard-return-types), but when it returns `false`, Angular tries other matching routes instead of completely blocking navigation.

```
export const featureToggleGuard: CanMatchFn = (
  route: Route,
  segments: UrlSegment[],
  currentSnapshot: PartialMatchRouteSnapshot,
) => {
  const featureService = inject(FeatureService);
  return featureService.isFeatureEnabled('newDashboard');
};
```

It can also allow you to use different components for the same path.

### routes.ts

```
const routes: Routes = [
  {
    path: 'dashboard',
    component: AdminDashboard,
    canMatch: [adminGuard],
  },
  {
    path: 'dashboard',
    component: UserDashboard,
    canMatch: [userGuard],
  },
];
```

In this example, when the user visits `/dashboard`, the first one that matches the correct guard will be used.

For more information, check out the [API docs for CanMatchFn](api/router/CanMatchFn).

## [Applying guards to routes](#applying-guards-to-routes)

Once you've created your route guards, you need to configure them in your route definitions.

Guards are specified as arrays in the route configuration in order to allow you to apply multiple guards to a single route. The router starts the guards in the order they appear in the array, but it does not wait for one guard to finish before starting the next. Guards that return a `Promise` or an `Observable` therefore run concurrently.

The array order determines which result takes priority. Navigation continues only if every guard returns `true`. Otherwise the router uses the result of the first guard in the array that did not return `true`, even if a later guard finished earlier.

**NOTE:** Because asynchronous guards run concurrently, a guard must not rely on work done by an earlier guard in the same array, such as loading the current user. Put that work in a service that both guards use, or combine the checks into a single guard.

```
import {Routes} from '@angular/router';
import {authGuard} from './guards/auth.guard';
import {adminGuard} from './guards/admin.guard';
import {canDeactivateGuard} from './guards/can-deactivate.guard';
import {featureToggleGuard} from './guards/feature-toggle.guard';

const routes: Routes = [
  // Basic CanActivate - requires authentication
  {
    path: 'dashboard',
    component: Dashboard,
    canActivate: [authGuard],
  },

  // Multiple CanActivate guards - requires authentication AND admin role
  {
    path: 'admin',
    component: Admin,
    canActivate: [authGuard, adminGuard],
  },

  // CanActivate + CanDeactivate - protected route with unsaved changes check
  {
    path: 'profile',
    component: Profile,
    canActivate: [authGuard],
    canDeactivate: [canDeactivateGuard],
  },

  // CanActivateChild - protects all child routes
  {
    path: 'users', // /user - NOT protected
    canActivateChild: [authGuard],
    children: [
      // /users/list - PROTECTED
      {path: 'list', component: UserList},
      // /users/detail/:id - PROTECTED
      {path: 'detail/:id', component: UserDetail},
    ],
  },

  // CanMatch - conditionally matches route based on feature flag
  {
    path: 'beta-feature',
    component: BetaFeature,
    canMatch: [featureToggleGuard],
  },

  // Fallback route if beta feature is disabled
  {
    path: 'beta-feature',
    component: ComingSoon,
  },
];
```

----
url: https://angular.dev/ai/webmcp
----

WebMCP is a [proposed web standard](https://github.com/webmachinelearning/webmcp/) that allows web applications to expose structured tools directly to AI agents running natively in the browser. Tools defined by an application allow AI assistants to interact with it directly, providing additional capabilities to the agent and reducing the need for DOM interactions.

For example, an application to register a new user might provide a WebMCP tool for a browser's AI agent to create the user directly rather than requiring the agent to go through a complex wizard UI via DOM interactions.

Angular provides experimental support for WebMCP, allowing you to easily register tools tied to your application's dependency injection lifecycle and automatically turn your Signal Forms into AI-ready tools.

**IMPORTANT:** The WebMCP spec is very early in its lifecycle and is undergoing frequent changes. As such, WebMCP support in Angular is currently [**experimental**](reference/releases#experimental). APIs are subject to change even outside of major versions.

## [Provide tools for the application](#provide-tools-for-the-application)

Use [`provideExperimentalWebMcpTools`](api/core/provideExperimentalWebMcpTools) in your application config to register tools for the entire lifecycle of the application. Tools provided this way are automatically registered when the application initializes and unregistered when the application is destroyed.

The `execute` callback is invoked in the injection context of the associated [`Injector`](/api/core/Injector), meaning you can [`inject`](api/core/inject) services directly.

### main.ts

```
import {Service, inject, provideExperimentalWebMcpTools} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {AppRoot} from './app-root';

@Service()
class Greeter {
  sayHello(): string {
    return 'Hello agent!';
  }
}

bootstrapApplication(AppRoot, {
  providers: [
    provideExperimentalWebMcpTools([
      {
        name: 'greet',
        description: 'Greets the agent.',
        inputSchema: {type: 'object', properties: {}},
        execute: () => {
          const greeter = inject(Greeter);

          return {content: [{type: 'text', text: greeter.sayHello()}]};
        },
      },
    ]),
  ],
});
```

### [Define tool parameters](#define-tool-parameters)

When a tool requires input from the AI assistant, define the expected arguments inside `inputSchema` using [JSON Schema](https://json-schema.org/) syntax. Angular automatically infers the parameter types passed into your `execute` callback based on the schema definition.

### main.ts

```
import {provideExperimentalWebMcpTools} from '@angular/core';
import {bootstrapApplication} from '@angular/platform-browser';
import {AppRoot} from './app-root';

bootstrapApplication(AppRoot, {
  providers: [
    provideExperimentalWebMcpTools([
      {
        name: 'searchCatalog',
        description: 'Searches the store catalog for products matching a query.',
        inputSchema: {
          type: 'object',
          properties: {
            query: {
              type: 'string',
              description: 'The search keywords.',
            },
            maxResults: {
              type: 'number',
              description: 'Maximum number of results to return.',
            },
          },
          required: ['query'],
          additionalProperties: false,
        },
        execute: ({query, maxResults}) => {
          // Type of `query` is inferred as `string`.
          // Type of `maxResults` is inferred as `number | undefined`.

          // Consider validating this at runtime, since inputs may not be validated to match the schema.
          if (typeof query !== 'string') throw new Error(`Bad query: ${query}`);
          if (typeof maxResults !== 'number' && maxResults !== undefined)
            throw new Error(`Bad maxResults: ${maxResults}`);

          const limit = maxResults ?? 5;
          return {
            content: [{type: 'text', text: `Returning up to ${limit} results for "${query}".`}],
          };
        },
      },
    ]),
  ],
});
```

**TIP:** Use `required: ['param1', 'param2', ...]` to remove `undefined` from the types of those parameters and use `additionalProperties: false` to restrict the argument object's type to only these parameters.

## [Provide tools for a route](#provide-tools-for-a-route)

When building complex applications, you may only want certain tools available when the user is viewing specific routes. You can achieve this by providing tools directly in route definitions.

### routes.ts

```
import {provideExperimentalWebMcpTools} from '@angular/core';
import {Routes} from '@angular/router';

export const routes: Routes = [
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard').then((m) => m.Dashboard),
    providers: [
      provideExperimentalWebMcpTools([
        {
          name: 'exportDashboardReports',
          description: 'Exports the current dashboard analytics.',
          inputSchema: {type: 'object', properties: {}},
          execute: () => ({
            content: [{type: 'text', text: 'Dashboard export successfully triggered.'}],
          }),
        },
      ]),
    ],
  },
];
```

**NOTE:** When registering tools to a particular route, consider configuring the router to use [`withAutoCleanupInjectors`](api/router/withAutoCleanupInjectors) to ensure tools are automatically *unregistered* when the user navigates away from the route. Without this option, WebMCP tools declared on routes will remain accessible to AI agents even after the user has navigated to a different route.

### app.config.ts

```
import {ApplicationConfig} from '@angular/core';
import {provideRouter, withAutoCleanupInjectors} from '@angular/router';
import {routes} from './routes';

export const appConfig: ApplicationConfig = {
  providers: [provideRouter(routes, withAutoCleanupInjectors())],
};
```

## [Provide tools within services](#provide-tools-within-services)

For dynamic use cases, the [`declareExperimentalWebMcpTool`](api/core/declareExperimentalWebMcpTool) function registers a tool directly within an injection context and automatically unregisters it when that context is destroyed.

### counter.ts

```
import {Service, declareExperimentalWebMcpTool, signal, inject} from '@angular/core';

@Service()
export class Counter {
  readonly count = signal(0);

  constructor() {
    declareExperimentalWebMcpTool({
      name: 'getCounter',
      description: 'Reads the global counter.',
      inputSchema: {type: 'object', properties: {}},
      execute: () => ({
        content: [{type: 'text', text: `The count is: ${this.count()}.`}],
      }),
    });
  }
}
```

While [`declareExperimentalWebMcpTool`](/api/core/declareExperimentalWebMcpTool) works in any injection context, watch out for [name collisions](#name-collisions) and prefer using it in root services.

## [Implicit tools in Signal Forms](#implicit-tools-in-signal-forms)

You can create a WebMCP tool implicitly from an existing Angular [Signal Form](essentials/signal-forms) with minimal configuration. Angular converts your form models into rich WebMCP tools, effectively supporting highly dynamic forms without requiring you to manually write JSON schemas or event handlers.

### [Enable the WebMCP forms feature](#enable-the-webmcp-forms-feature)

First, add [`provideExperimentalWebMcpForms`](api/forms/signals/provideExperimentalWebMcpForms) to your root application providers:

### main.ts

```
import {bootstrapApplication} from '@angular/platform-browser';
import {provideExperimentalWebMcpForms} from '@angular/forms/signals';
import {AppRoot} from './app-root';

bootstrapApplication(AppRoot, {
  providers: [provideExperimentalWebMcpForms()],
});
```

### [Opt in a Signal Form](#opt-in-a-signal-form)

Second, when defining a Signal Form using [`form`](api/forms/signals/form), pass the `experimentalWebMcpTool` configuration option to opt-in to an implicit WebMCP tool. Angular will inspect your form's data model and automatically generate a JSON schema for connected AI agents.

### user-registration.ts

```
import {Component, signal} from '@angular/core';
import {form, required, minLength} from '@angular/forms/signals';

@Component({
  selector: 'app-user-registration',
  templateUrl: './user-registration.html',
})
export class UserRegistration {
  private readonly model = signal({
    firstName: '',
    lastName: '',
    age: 0,
    hobbies: ['Web Development'],
  });

  readonly userForm = form(
    this.model,
    (f) => {
      required(f.firstName, {message: 'First name is mandatory.'});
      required(f.lastName, {message: 'Last name is mandatory.'});
    },
    {
      // Implicitly registers a WebMCP tool named `registerUser` with parameters derived from `model`.
      experimentalWebMcpTool: {
        name: 'registerUser',
        description: 'Registers a new user.',
      },
      submission: {
        action: async (formValue) => {
          console.log('Submitting user:', formValue().value());
          // ...
        },
      },
    },
  );
}
```

In this example, Angular generates a WebMCP tool with a JSON schema which:

1. includes `firstName`, `lastName`, `age`, and `hobbies` as parameters inferred from the initial value of the [`model`](/api/core/model) signal.
2. defines `firstName` and `lastName` as *required* fields as inferred from the [`required`](api/forms/signals/required) validator.
3. defines `hobbies` as an array of strings, allowing the agent to provide an arbitrary amount of hobbies.

Beyond inferring the input schema, Angular also connects the WebMCP tool to the form's validation logic and submission handler. This means the agent will observe any validation errors triggered by its inputs or any failures which happen during submission, allowing it to self-correct and potentially retry.

**NOTE:** Async validators are *not* triggered and should be handled by the submission action.

#### [Constraints](#constraints)

Angular infers the WebMCP schema from the initial value of your form model. This requires:

* Concrete initial values (`''`, `0`, `false`): Angular cannot infer data types from `null` or `undefined`.
* Non-empty arrays (`['Hello!']`): Angular cannot infer data types from an empty array and requires at least one initial value.

## [Best practices](#best-practices)

Keep the following best practices in mind:

### [Name collisions](#name-collisions)

WebMCP requires each tool to have a unique name and will throw an error if the same tool name is registered multiple times. This means calling [`declareExperimentalWebMcpTool`](/api/core/declareExperimentalWebMcpTool) or [`provideExperimentalWebMcpTools`](/api/core/provideExperimentalWebMcpTools) in a context where they might be registered multiple times (such as a component constructor) may lead to errors at runtime.

Prefer placing tools on application providers, route providers, or root services where possible. When putting tools on a component, including [implicit tools in Signal Forms](#implicit-tools-in-signal-forms), ensure that component is only ever rendered on the page at most *once* at any given time.

### [Validate tool inputs](#validate-tool-inputs)

Angular does not provide any implicit validation that the inputs provided by an agent actually match the defined JSON schema. Consider explicitly validating arguments to the `execute` function before using them to ensure reliability.

### [Testing](#testing)

Consider using a mock WebMCP implementation like [`@mcp-b/webmcp-polyfill`](https://www.npmjs.com/package/@mcp-b/webmcp-polyfill) to effectively unit test your tools.

----
url: https://angular.dev/api/ssr/ServerRoutePrerender
----

```
    
      interface ServerRoutePrerender extends Omit<ServerRouteCommon, 'status'> {}
    
    
```

Specifies that the route uses Static Site Generation (SSG) mode.

Fallback cannot be specified unless `getPrerenderParams` is used.

The path associated with this route.

Optional additional headers to include in the response for this route.

Jump to details

----
url: https://angular.dev/guide/animations/css
----

CSS offers a robust set of tools for you to create beautiful and engaging animations within your application.

## [How to write animations in native CSS](#how-to-write-animations-in-native-css)

If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here are a few of them: [MDN's CSS Animations guide](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animations/Using_CSS_animations) [W3Schools CSS3 Animations guide](https://www.w3schools.com/css/css3_animations.asp) [The Complete CSS Animations Tutorial](https://www.lambdatest.com/blog/css-animations-tutorial/) [CSS Animation for Beginners](https://thoughtbot.com/blog/css-animation-for-beginners)

and a couple of videos: [Learn CSS Animation in 9 Minutes](https://www.youtube.com/watch?v=z2LQYsZhsFw) [Net Ninja CSS Animation Tutorial Playlist](https://www.youtube.com/watch?v=jgw82b5Y2MU\&list=PL4cUxeGkcC9iGYgmEd2dm3zAKzyCGDtM5)

Check out some of these guides and tutorials, then come back to this guide.

## [Creating Reusable Animations](#creating-reusable-animations)

You can create reusable animations that can be shared across your application using `@keyframes`. Define keyframe animations in a shared CSS file, and you'll be able to re-use those keyframe animations wherever you want within your application.

### animations.css

```
@keyframes sharedAnimation {
  to {
    height: 0;
    opacity: 1;
    background-color: 'red';
  }
}

.animated-class {
  animation: sharedAnimation 1s;
}
```

Adding the class `animated-class` to an element would trigger the animation on that element.

## [Animating a Transition](#animating-a-transition)

### [Animating State and Styles](#animating-state-and-styles)

You may want to animate between two different states, for example when an element is opened or closed. You can accomplish this by using CSS classes, either with a keyframe animation or transition styling.

### animations.css

```
.open {
  height: '200px';
  opacity: 1;
  background-color: 'yellow';
  transition: all 1s;
}

.closed {
  height: '100px';
  opacity: 0.8;
  background-color: 'blue';
  transition: all 1s;
}
```

Triggering the `open` or `closed` state is done by toggling classes on the element in your component. You can find examples of how to do this in our [template guide](guide/templates/binding#css-class-and-style-property-bindings).

You can see similar examples in the template guide for [animating styles directly](guide/templates/binding#css-style-properties).

### [Transitions, Timing, and Easing](#transitions-timing-and-easing)

Animating often requires adjusting timing, delays, and easing behaviors. This can be done using several css properties or shorthand properties.

Specify `animation-duration`, `animation-delay`, and `animation-timing-function` for a keyframe animation in CSS, or alternatively use the `animation` shorthand property.

### animations.css

```
.example-element {
  animation-duration: 1s;
  animation-delay: 500ms;
  animation-timing-function: ease-in-out;
}

.example-shorthand {
  animation: exampleAnimation 1s ease-in-out 500ms;
}
```

Similarly, you can use `transition-duration`, `transition-delay`, and `transition-timing-function` and the `transition` shorthand for animations that are not using `@keyframes`.

### animations.css

```
.example-element {
  transition-duration: 1s;
  transition-delay: 500ms;
  transition-timing-function: ease-in-out;
  transition-property: margin-right;
}

.example-shorthand {
  transition: margin-right 1s ease-in-out 500ms;
}
```

### [Triggering an Animation](#triggering-an-animation)

Animations can be triggered by toggling CSS styles or classes. Once a class is present on an element, the animation will occur. Removing the class will revert the element back to whatever CSS is defined for that element. Here's an example:

### open-close.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-open-close',
  templateUrl: 'open-close.html',
  styleUrls: ['open-close.css'],
})
export class OpenClose {
  isOpen = signal(true);
  toggle() {
    this.isOpen.update((isOpen) => !isOpen);
  }
}
```

### open-close.html

```
<h2>Open / Close Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>

<div class="open-close-container" [class.open]="isOpen()">
  <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
```

### open-close.css

```
:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  font-weight: bold;
  font-size: 20px;
  height: 100px;
  opacity: 0.8;
  background: #3b82f6;
  color: #ebebeb;
  transition-property: height, opacity, background-color, color;
  transition-duration: 1s;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}

.open {
  transition-duration: 0.5s;
  height: 200px;
  opacity: 1;
  background: #475569;
  color: #f9fafb;
}
```

## [Transition and Triggers](#transition-and-triggers)

### [Animating Auto Height](#animating-auto-height)

You can use CSS Grid to animate to auto height.

### auto-height.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-auto-height',
  templateUrl: 'auto-height.html',
  styleUrls: ['auto-height.css'],
})
export class AutoHeight {
  isOpen = signal(true);
  toggle() {
    this.isOpen.update((isOpen) => !isOpen);
  }
}
```

### auto-height.html

```
<h2>Auto Height Example</h2>

<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>

<div class="container" [class.open]="isOpen()">
  <div class="content">
    <p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
  </div>
</div>
```

### auto-height.css

```
.container {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  transition: grid-template-rows 1s;
}

.container.open {
  grid-template-rows: 1fr;
}

.container .content {
  min-height: 0;
  transition: visibility 1s;
  padding: 0 20px;
  visibility: hidden;
  margin-top: 1em;
  font-weight: bold;
  font-size: 20px;
  background: #3b82f6;
  color: #ebebeb;
  overflow: hidden;
}

.container.open .content {
  visibility: visible;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

If you don't have to worry about supporting all browsers, you can also check out `calc-size()`, which is the true solution to animating auto height. See [MDN's docs](https://developer.mozilla.org/en-US/docs/Web/CSS/calc-size) and [this tutorial](https://frontendmasters.com/blog/one-of-the-boss-battles-of-css-is-almost-won-transitioning-to-auto/) for more information.

### [Animate entering and leaving a view](#animate-entering-and-leaving-a-view)

You can create animations for when an item enters a view or leaves a view. Let's start by looking at how to animate an element entering a view. We'll do this with `animate.enter`, which will apply animation classes when an element enters the view.

### insert.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-insert',
  templateUrl: 'insert.html',
  styleUrls: ['insert.css'],
})
export class Insert {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### insert.html

```
<h2>Insert Element Example</h2>

<nav>
  <button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>

@if (isShown()) {
  <div class="insert-container" animate.enter="enter-animation">
    <p>The box is inserted</p>
  </div>
}
```

### insert.css

```
:host {
  display: block;
}

.insert-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
}

.insert-container p {
  margin: 0;
}

.enter-animation {
  animation: slide-fade 1s;
}

@keyframes slide-fade {
  from {
    opacity: 0;
    transform: translateY(20px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

Animating an element when it leaves the view is similar to animating when entering a view. Use `animate.leave` to specify which CSS classes to apply when the element leaves the view.

### remove.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-remove',
  templateUrl: 'remove.html',
  styleUrls: ['remove.css'],
})
export class Remove {
  isShown = signal(false);

  toggle() {
    this.isShown.update((isShown) => !isShown);
  }
}
```

### remove.html

```
<h2>Remove Element Example</h2>

<nav>
  <button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>

@if (isShown()) {
  <div class="insert-container" animate.leave="deleting">
    <p>The box is inserted</p>
  </div>
}
```

### remove.css

```
:host {
  display: block;
}

.insert-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px;
  font-weight: bold;
  font-size: 20px;
  opacity: 1;
  transition: opacity 200ms ease-in;

  @starting-style {
    opacity: 0;
  }
}

.insert-container p {
  margin: 0;
}

.deleting {
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 500ms ease-out,
    transform 500ms ease-out;
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

**NOTE:** Child `animate.leave` animations fire only within the same component template. Nested component `animate.leave` animations will not fire when a parent element is removed.

For more information on `animate.enter` and `animate.leave`, see the [Enter and Leave animations guide](guide/animations).

### [Animating increment and decrement](#animating-increment-and-decrement)

Animating on increment and decrement is a common pattern in applications. Here's an example of how you can accomplish that behavior.

### increment-decrement.ts

```
import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';

@Component({
  selector: 'app-increment-decrement',
  templateUrl: 'increment-decrement.html',
  styleUrls: ['increment-decrement.css'],
})
export class IncrementDecrement implements OnInit {
  num = signal(0);
  el = viewChild<ElementRef<HTMLParagraphElement>>('el');

  ngOnInit() {
    this.el()?.nativeElement.addEventListener('animationend', (ev) => {
      if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) {
        this.animationFinished();
      }
    });
  }

  modify(n: number) {
    const targetClass = n > 0 ? 'increment' : 'decrement';
    this.num.update((v) => (v += n));
    this.el()?.nativeElement.classList.add(targetClass);
  }

  animationFinished() {
    this.el()?.nativeElement.classList.remove('increment', 'decrement');
  }

  ngOnDestroy() {
    this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished);
  }
}
```

### increment-decrement.html

```
<h3>Increment and Decrement Example</h3>
<section>
  <p #el>Number {{ num() }}</p>
  <div class="controls">
    <button type="button" (click)="modify(1)">+</button>
    <button type="button" (click)="modify(-1)">-</button>
  </div>
</section>
```

### increment-decrement.css

```
:host {
  display: block;
  font-size: 32px;
  margin: 20px;
  text-align: center;
}

section {
  border: 1px solid lightgray;
  border-radius: 50px;
}

p {
  display: inline-block;
  margin: 2rem 0;
  text-transform: uppercase;
}

.increment {
  animation: increment 300ms;
}

.decrement {
  animation: decrement 300ms;
}

.controls {
  padding-bottom: 2rem;
}

button {
  font: inherit;
  border: 0;
  background: lightgray;
  width: 50px;
  border-radius: 10px;
}

button + button {
  margin-left: 10px;
}

@keyframes increment {
  33% {
    color: green;
    transform: scale(1.3, 1.2);
  }
  66% {
    color: green;
    transform: scale(1.2, 1.2);
  }
  100% {
    transform: scale(1, 1);
  }
}

@keyframes decrement {
  33% {
    color: red;
    transform: scale(0.8, 0.9);
  }
  66% {
    color: red;
    transform: scale(0.9, 0.9);
  }
  100% {
    transform: scale(1, 1);
  }
}
```

### [Disabling an animation or all animations](#disabling-an-animation-or-all-animations)

If you'd like to disable the animations that you've specified, you have multiple options.

1. Create a custom class that forces animation and transition to `none`.

```
.no-animation {
  animation: none !important;
  transition: none !important;
}
```

Applying this class to an element prevents any animation from firing on that element. You could alternatively scope this to your entire DOM or section of your DOM to enforce this behavior. However, this prevents animation events from firing. If you are awaiting animation events for element removal, this solution won't work. A workaround is to set durations to 1 millisecond instead.

2. Use the [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) media query to ensure no animations play for users that prefer less animation.

3. Prevent adding animation classes programmatically

### [Animation Callbacks](#animation-callbacks)

If you have actions you would like to execute at certain points during animations, there are a number of available events you can listen to. Here's a few of them.

Animations are often more complicated than just a simple fade in or fade out. You may have lots of complicated sequences of animations you may want to run. Let's take a look at some of those possible scenarios.

### [Staggering animations in a list](#staggering-animations-in-a-list)

One common effect is to stagger the animations of each item in a list to create a cascade effect. This can be accomplished by utilizing `animation-delay` or `transition-delay`. Here is an example of what that CSS might look like.

### stagger.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-stagger',
  templateUrl: './stagger.html',
  styleUrls: ['stagger.css'],
})
export class Stagger {
  show = signal(true);
  items = [1, 2, 3];

  refresh() {
    this.show.set(false);
    setTimeout(() => {
      this.show.set(true);
    }, 10);
  }
}
```

### stagger.html

```
<h1>Stagger Example</h1>
<button type="button" class="toggle-btn" (click)="refresh()">Refresh</button>
<div class="items-container">
  @if (show()) {
    <ul class="items">
      @for (item of items; track $index) {
        <li class="item" style="--index: {{ $index }}">{{ item }}</li>
      }
    </ul>
  }
</div>
```

### stagger.css

```
.items-container {
  min-height: 4.5rem;
}

.items {
  list-style: none;
  padding: 0;
  margin: 0;
}

.items .item {
  transition-property: opacity, transform;
  transition-duration: 500ms;
  transition-delay: calc(200ms * var(--index));

  @starting-style {
    opacity: 0;
    transform: translateX(-10px);
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

### [Parallel Animations](#parallel-animations)

You can apply multiple animations to an element at once using the `animation` shorthand property. Each can have their own durations and delays. This allows you to compose animations together and create complicated effects.

```
.target-element {
  animation:
    rotate 3s,
    fade-in 2s;
}
```

In this example, the `rotate` and `fade-in` animations fire at the same time, but have different durations.

### [Animating the items of a reordering list](#animating-the-items-of-a-reordering-list)

Items in a `@for` loop will be removed and re-added, which will fire off animations using `@starting-styles` for entry animations. Alternatively, you can use `animate.enter` for this same behavior. Use `animate.leave` to animate elements as they are removed, as seen in the example below.

### reorder.ts

```
import {Component, signal} from '@angular/core';

@Component({
  selector: 'app-reorder',
  templateUrl: './reorder.html',
  styleUrls: ['reorder.css'],
})
export class Reorder {
  show = signal(true);
  items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];

  randomize() {
    const randItems = [...this.items];
    const newItems = [];
    for (let i of this.items) {
      const max: number = this.items.length - newItems.length;
      const randNum = Math.floor(Math.random() * max);
      newItems.push(...randItems.splice(randNum, 1));
    }

    this.items = newItems;
  }
}
```

### reorder.html

```
<h1>Reordering List Example</h1>
<button type="button" class="toggle-btn" (click)="randomize()">Randomize</button>

<ul class="items">
  @for (item of items; track item) {
    <li class="item" animate.leave="fade">{{ item }}</li>
  }
</ul>
```

### reorder.css

```
.items {
  list-style: none;
  padding: 0;
  margin: 0;
}

.items .item {
  transition-property: opacity, transform;
  transition-duration: 500ms;

  @starting-style {
    opacity: 0;
    transform: translateX(-10px);
  }
}

.items .item.fade {
  animation: fade-out 500ms;
}

@keyframes fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

.toggle-btn {
  background: transparent;
  border: 1px solid var(--primary-contrast, black);
  color: var(--primary-contrast, black);
  padding: 10px 24px;
  border-radius: 8px;
  cursor: pointer;
}
```

## [Programmatic control of animations](#programmatic-control-of-animations)

You can retrieve animations off an element directly using [`Element.getAnimations()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAnimations). This returns an array of every [`Animation`](https://developer.mozilla.org/en-US/docs/Web/API/Animation) on that element. You can use the `Animation` API to do much more than you could with what the [`AnimationPlayer`](/api/animations/AnimationPlayer) from the animations package offered. From here you can `cancel()`, `play()`, `pause()`, `reverse()` and much more. This native API should provide everything you need to control your animations.

## [More on Angular animations](#more-on-angular-animations)

You might also be interested in the following:

----
url: https://angular.dev/api/core/InjectOptions
----

```
    
      interface InjectOptions {}
    
    
```

Use optional injection, and return `null` if the requested token is not found.

Start injection at the parent of the current injector.

Only query the current injector for the token, and don't fall back to the parent injector if it's not found.

Stop injection at the host component's injector. Only relevant when injecting from an element injector, and a no-op for environment injectors.

Jump to details

----
url: https://angular.dev/api/cdk/testing/HarnessEnvironment
----

```
    
      abstract class HarnessEnvironment<E> implements HarnessLoader ,LocatorFactory {}
    
    
```

@paramrawRootElement`E`

The native root element of this [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment).

@returns`HarnessEnvironment<E>`

The root element of this [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) as a [`TestElement`](/api/cdk/testing/TestElement).

Gets a locator factory rooted at the document root.

@returns`LocatorFactory`

Creates an asynchronous locator function that can be used to find a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instance or element under the root element of this [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await lf.locatorFor(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await lf.locatorFor('div', DivHarness)() // Gets a `TestElement` instance for #d1
await lf.locatorFor('span')()            // Throws because the `Promise` rejects
```

@paramqueries`T`

A list of queries specifying which harnesses and elements to search for:

* A `string` searches for elements matching the CSS selector specified by the string.
* A [`ComponentHarness`](/api/cdk/testing/ComponentHarness) constructor searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given class.
* A [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given predicate.

@returns`() => Promise<LocatorFnResult<T>>`

Creates an asynchronous locator function that can be used to find a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instance or element under the root element of this `HarnessEnvironmnet`.

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
await lf.locatorForOptional(DivHarness, 'div')() // Gets a `DivHarness` instance for #d1
await lf.locatorForOptional('div', DivHarness)() // Gets a `TestElement` instance for #d1
await lf.locatorForOptional('span')()            // Gets `null`
```

@paramqueries`T`

A list of queries specifying which harnesses and elements to search for:

* A `string` searches for elements matching the CSS selector specified by the string.
* A [`ComponentHarness`](/api/cdk/testing/ComponentHarness) constructor searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given class.
* A [`HarnessPredicate`](/api/cdk/testing/HarnessPredicate) searches for [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances matching the given predicate.

@returns`() => Promise<LocatorFnResult<T> | null>`

Creates an asynchronous locator function that can be used to find [`ComponentHarness`](/api/cdk/testing/ComponentHarness) instances or elements under the root element of this [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment).

For example, given the following DOM and assuming `DivHarness.hostSelector` is `'div'` and `IdIsD1Harness.hostSelector` is `'#d1'`

```
<div id="d1"></div><div id="d2"></div>
```

then we expect:

```
// Gets [DivHarness for #d1, TestElement for #d1, DivHarness for #d2, TestElement for #d2]
await lf.locatorForAll(DivHarness, 'div')()
// Gets [TestElement for #d1, TestElement for #d2]
await lf.locatorForAll('div', '#d1')()
// Gets [DivHarness for #d1, IdIsD1Harness for #d1, DivHarness for #d2]
await lf.locatorForAll(DivHarness, IdIsD1Harness)()
// Gets []
await lf.locatorForAll('span')()
```

Gets a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance for an element under the root of this [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment).

@paramselector`string`

The selector for the root element.

@returns`Promise<HarnessLoader>`

Gets a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) instance for an element under the root of this [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment).

Searches for an instance of the component corresponding to the given harness type under the [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment)'s root element, and returns a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for that instance. If multiple matching components are found, a harness for the first one is returned. If no matching component is found, an error is thrown.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<T>`

Searches for an instance of the component corresponding to the given harness type under the [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment)'s root element, and returns a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for that instance. If multiple matching components are found, a harness for the first one is returned. If no matching component is found, null is returned.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<T | null>`

Searches for an instance of the component corresponding to the given harness type and index under the [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment)'s root element, and returns a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for that instance. The index specifies the offset of the component to find. If no matching component is found at that index, an error is thrown.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@paramoffset`number`

@returns`Promise<T>`

Searches for all instances of the component corresponding to the given harness type under the [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment)'s root element, and returns a list [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for each instance.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<T[]>`

Searches for all instance of the component corresponding to the given harness type under the [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment)'s root element, and returns the number that were found.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<number>`

Searches for an instance of the component corresponding to the given harness type under the [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment)'s root element, and returns a boolean indicating if any were found.

@paramquery`HarnessQuery<T>`

A query for a harness to create

@returns`Promise<boolean>`

Searches for an element with the given selector under the evironment's root element, and returns a [`HarnessLoader`](/api/cdk/testing/HarnessLoader) rooted at the matching element. If multiple elements match the selector, the first is used. If no elements match, an error is thrown.

@paramselector`string`

The selector for the root element of the new [`HarnessLoader`](/api/cdk/testing/HarnessLoader)

@returns`Promise<HarnessLoader>`

Searches for all elements with the given selector under the environment's root element, and returns an array of [`HarnessLoader`](/api/cdk/testing/HarnessLoader)s, one for each matching element, rooted at that element.

@paramselector`string`

The selector for the root element of the new [`HarnessLoader`](/api/cdk/testing/HarnessLoader)

@returns`Promise<HarnessLoader[]>`

Creates a [`ComponentHarness`](/api/cdk/testing/ComponentHarness) for the given harness type with the given raw host element.

@paramharnessType`ComponentHarnessConstructor<T>`

@paramelement`E`

@returns`T`

Flushes change detection and async tasks captured in the Angular zone. In most cases it should not be necessary to call this manually. However, there may be some edge cases where it is needed to fully flush animation events. This is an abstrct method that must be implemented by subclasses.

@returns`Promise<void>`

Waits for all scheduled or running async tasks to complete. This allows harness authors to wait for async tasks outside of the Angular zone. This is an abstrct method that must be implemented by subclasses.

@returns`Promise<void>`

Gets the root element for the document.

@returns`E`

Creates a [`TestElement`](/api/cdk/testing/TestElement) from a raw element.

@paramelement`E`

@returns`TestElement`

Creates a [`HarnessEnvironment`](/api/cdk/testing/HarnessEnvironment) rooted at the given raw element.

@paramelement`E`

@returns`HarnessEnvironment<E>`

Gets a list of all elements matching the given selector under this environment's root element.

@paramselector`string`

@returns`Promise<E[]>`

Jump to details

----
url: https://angular.dev/api/router/DefaultExport
----

```
    
      interface DefaultExport<T> {}
    
    
```

Default exports are bound under the name `"default"`, per the ES Module spec: <https://tc39.es/ecma262/#table-export-forms-mapping-to-exportentry-records>

Jump to details

----
url: https://angular.dev/api/ssr/RequestHandlerFunction
----

```
    
      type RequestHandlerFunction = (request: Request) => Promise<Response | null> | null | Response
    
    
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8104
----

This diagnostic ensures that attributes which have the "special" Angular binding prefix (`attr.`, [`style.`](/api/animations/style), and `class.`) are interpreted as bindings.

```
<div attr.id="my-id"></div>
```

## [What's wrong with that?](/extended-diagnostics/NG8104#whats-wrong-with-that)

In this example, `attr.id` is interpreted as a regular attribute and will appear as-is in the final HTML (`<div attr.id="my-id"></div>`). This is likely not the intent of the developer. Instead, the intent is likely to set the `id` attribute (`<div id="my-id"></div>`).

## [What should I do instead?](/extended-diagnostics/NG8104#what-should-i-do-instead)

When binding to `attr.`, `class.`, or [`style.`](/api/animations/style), ensure you use the Angular template binding syntax (`[]`).

```
<div [attr.id]="'my-id'"></div>
<div [style.color]="'red'"></div>
<div [class.large]="true"></div>
```

## [Configuration requirements](/extended-diagnostics/NG8104#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. `textAttributeNotBinding` has no additional requirements beyond `strictTemplates`.

## [What if I can't avoid this?](/extended-diagnostics/NG8104#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "textAttributeNotBinding": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/platform-browser/provideProtractorTestingSupport
----

```
function provideProtractorTestingSupport(options?: {
  usePendingTasksForStability?: boolean | undefined;
}): Provider[];
```

@paramoptions`{ usePendingTasksForStability?: boolean | undefined; }`

@returns`Provider[]`

An array of providers required to setup Testability for an application and make it available for testing using Protractor.

Jump to details

----
url: https://angular.dev/api/router/withRouterConfig
----

```
function withRouterConfig(
  options: RouterConfigOptions,
): RouterConfigurationFeature;
```

@paramoptions`RouterConfigOptions`

A set of parameters to configure Router, see [`RouterConfigOptions`](/api/router/RouterConfigOptions) for additional information.

@returns`RouterConfigurationFeature`

A set of providers for use with [`provideRouter`](/api/router/provideRouter).

## [Usage Notes](#usage-notes)

Basic example of how you can provide extra configuration options:

```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
  {
    providers: [
      provideRouter(appRoutes, withRouterConfig({
         onSameUrlNavigation: 'reload'
      }))
    ]
  }
);
```

Jump to details

----
url: https://angular.dev/errors/NG0919
----

Angular detected a circular dependency between components, directives, or pipes. This error occurs when component A imports component B, and component B (directly or indirectly) imports component A, creating a cycle.

This circular reference prevents Angular from properly initializing the components, resulting in an error like:

```
NG0919: Cannot read @Component metadata. This can indicate a runtime circular dependency in your app that needs to be resolved.
```

In older Angular versions, you might instead see an error like:

```
Cannot read properties of undefined (reading 'ɵcmp')
```

## [Common Causes](#common-causes)

### [Mutual Component Imports](#mutual-component-imports)

The most common cause is when two components import each other:

### parent.ts

```
import {Component} from '@angular/core';
import {Child} from './child';

@Component({
  selector: 'app-parent',
  imports: [Child],
  template: '<app-child/>',
})
export class Parent {}
```

### child.ts

```
import {Component} from '@angular/core';
import {Parent} from './parent';

@Component({
  selector: 'app-child',
  imports: [Parent],
  template: '<app-parent/>',
})
export class Child {}
```

### [Indirect Circular References](#indirect-circular-references)

Circular dependencies can also occur through intermediate files:

```
ComponentA -> ComponentB -> ComponentC -> ComponentA
```

## [Resolving the error](#resolving-the-error)

### [Refactor shared logic](#refactor-shared-logic)

Move shared functionality to a separate file that doesn't import either component:

### shared.service.ts

```
import {Service} from '@angular/core';

@Service()
export class SharedService {
  // Shared logic here
}
```

### [Use type-only imports](#use-type-only-imports)

If you only need types for TypeScript, use `import type`:

```
import type {Parent} from './parent';
```

Type-only imports are erased at compile time and don't contribute to runtime circular dependencies.

### [Restructure component hierarchy](#restructure-component-hierarchy)

Consider whether the circular dependency indicates a design issue. Often, extracting shared functionality into a third component or service is the cleanest solution.

### [Debugging complex circular dependencies](#debugging-complex-circular-dependencies)

For complex applications with many modules, circular dependencies can be difficult to identify manually. Consider using tools like [madge](https://www.npmjs.com/package/madge) to visualize and detect circular imports:

```
# Install madge
npm install -g madge

# Check for circular dependencies
madge --circular --extensions ts ./src

# Generate a visual graph
madge --circular --extensions ts --image graph.svg ./src
```

These tools can help identify circular dependency chains across your entire project and generate visual dependency graphs.

----
url: https://angular.dev/errors/NG8002
----

[](https://www.youtube.com/watch?v=wfLkB3RsSJM\&autoplay=1)

An attribute or property cannot be resolved during compilation.

This error arises when attempting to bind to a property that does not exist. Any property binding must correspond to either:

* A native property on the HTML element, or
* An [`input()`](/api/core/input)/[`@Input()`](/api/core/Input) property of a component or directive applied to the element.

The runtime error for this is `NG0303: Can't bind to '${propName}' since it isn't a known property of '${tagName}'`.

## [Debugging the error](#debugging-the-error)

Look at documentation for the specific [binding syntax](guide/templates) used. This is usually a typo or incorrect import. There may also be a missing direction with property selector 'name' or missing input.

----
url: https://angular.dev/tutorials/signal-forms/6-next-steps
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signal-forms/5-add-submission)

[](/tutorials/signal-forms/5-add-submission)

You've done it! You completed the Signal Forms tutorial and built a login form from scratch.

## [What you learned](#what-you-learned)

Throughout this tutorial, you learned the fundamentals of Angular Signal Forms:

1. **Form Models** - Creating type-safe form data with signals and the [`form()`](/api/forms/signals/form) function
2. **Field Binding** - Using the `[formField]` directive for two-way data binding and displaying the field with `value()`
3. **Validation** - Applying built-in validators (such as `required()`, `email()`) with custom messages
4. **Error Display** - Showing validation errors conditionally based on field state
5. **Form Submission** - Handling form submission with the [`submit()`](/api/forms/signals/submit) function

## [Next steps](#next-steps)

Ready to learn more? Here are recommended next steps:

### [Explore the documentation](#explore-the-documentation)

* **[Signal Forms Overview](guide/forms/signals/overview)** - Introduction to Signal Forms and when to use them
* **[Form Models Guide](guide/forms/signals/models)** - Deep dive into form models and data management
* **[Validation Guide](guide/forms/signals/validation)** - Comprehensive validation reference
* **[Field State Management Guide](guide/forms/signals/field-state-management)** - Advanced state patterns
* **[Custom Controls Guide](guide/forms/signals/custom-controls)** - Building reusable form components

## [Keep learning](#keep-learning)

Check the [official documentation](guide/forms/signals/overview) for the complete Signal Forms API.

Happy coding!

----
url: https://angular.dev/api/common/LowerCasePipe
----

```
{{ value_expression | lowercase }}
```

## [API](#api)

```
    
      class LowerCasePipe implements PipeTransform {
}
    
    
```

@paramvalue`string`

The string to transform to lower case.

@returns`string`

@paramvalue`null | undefined`

@returns`null`

@paramvalue`string | null | undefined`

@returns`string | null`

## [Description](#description)

Transforms text to all lower case.

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

The following example defines a view that allows the user to enter text, and then uses the pipe to convert the input text to all lower case.

```
@Component({
  selector: 'lowerupper-pipe',
  imports: [LowerCasePipe, UpperCasePipe],
  template: `<div>
    <label>Name: </label><input #name (keyup)="change(name.value)" type="text" />
    <p>In lowercase:</p>
    <pre>'{{ value | lowercase }}'</pre>
    <p>In uppercase:</p>
    <pre>'{{ value | uppercase }}'</pre>
  </div>`,
})
export class LowerUpperPipeComponent {
  value: string = '';
  change(value: string) {
    this.value = value;
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/rxjs-interop/pendingUntilEvent
----

```
function pendingUntilEvent<T>(
  injector?: Injector | undefined,
): MonoTypeOperatorFunction<T>;
```

@paraminjector`Injector | undefined`

The `Injector` to use during creation. If this is not provided, the current injection context will be used instead (via `inject`).

@returns`MonoTypeOperatorFunction<T>`

## [Description](#description)

Operator which makes the application unstable until the observable emits, completes, errors, or is unsubscribed.

Use this operator in observables whose subscriptions are important for rendering and should be included in SSR serialization.

Jump to details

----
url: https://angular.dev/api/core/viewChildren
----

```
    
      
    
    
```

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ debugName?: string | undefined; } | undefined`

@returns`Signal<readonly LocatorT[]>`

@paramlocator`string | ProviderToken<LocatorT>`

@paramopts`{ read: ProviderToken<ReadT>; debugName?: string | undefined; }`

@returns`Signal<readonly ReadT[]>`

## [Usage Notes](#usage-notes)

Create a children query in your component by declaring a class field and initializing it with the [`viewChildren()`](/api/core/viewChildren) function.

```
@Component({...})
export class TestComponent {
  divEls = viewChildren<ElementRef>('el');   // Signal<ReadonlyArray<ElementRef>>
}
```

Jump to details

----
url: https://angular.dev/api/animations/AnimationSequenceMetadata
----

```
    
      interface AnimationSequenceMetadata extends AnimationMetadata {}
    
    
```

An array of animation step objects.

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/api/platform-server/provideServerRendering
----

```
function provideServerRendering(
  options?: { maxResponseBodySize: number } | undefined,
): EnvironmentProviders;
```

@paramoptions`{ maxResponseBodySize: number; } | undefined`

An object to configure the server providers. Currently supports the following options:

* `maxResponseBodySize`: The maximum allowed response body size when using the Fetch API.

@returns`EnvironmentProviders`

A set of providers to setup the server.

## [Usage Notes](#usage-notes)

Basic example of how you can add server support to your application:

```
bootstrapApplication(AppComponent, {
  providers: [provideServerRendering()]
});
```

Jump to details

----
url: https://angular.dev/api/ssr/node/createWebRequestFromNodeRequest
----

```
function createWebRequestFromNodeRequest(
  nodeRequest: IncomingMessage | Http2ServerRequest,
  trustProxyHeaders?: boolean | readonly string[] | undefined,
): Request;
```

@paramnodeRequest`IncomingMessage | Http2ServerRequest`

* The Node.js request object (`IncomingMessage` or `Http2ServerRequest`) to convert.

@paramtrustProxyHeaders`boolean | readonly string[] | undefined`

* A boolean or an array of proxy headers to trust when constructing the request URL.

@returns`Request`

A Web Standard `Request` object.

## [Description](#description)

Converts a Node.js `IncomingMessage` or `Http2ServerRequest` into a Web Standard `Request` object.

This function adapts the Node.js request objects to a format that can be used by web platform APIs.

## [Usage Notes](#usage-notes)

When `trustProxyHeaders` is enabled, headers such as `Forwarded`, `X-Forwarded-Host`, and `X-Forwarded-Prefix` should ideally be strictly validated at a higher infrastructure level (e.g., at the reverse proxy or API gateway) before reaching the application.

Jump to details

----
url: https://angular.dev/api/common/NoTrailingSlashPathLocationStrategy
----

```
    
      class NoTrailingSlashPathLocationStrategy extends PathLocationStrategy {}
    
    
```

----
url: https://angular.dev/api/core/SecurityContext
----

```
    
      enum SecurityContext {}
    
    
```

## [Description](#description)

A SecurityContext marks a location that has dangerous security implications, e.g. a DOM property like `innerHTML` that could cause Cross Site Scripting (XSS) security bugs when improperly handled.

See DomSanitizer for more details on security in Angular applications.

Jump to details

----
url: https://angular.dev/api/router/mapToCanActivateChild
----

```
function mapToCanActivateChild(
  providers: Type<CanActivateChild>[],
): CanActivateChildFn[];
```

@paramproviders`Type<CanActivateChild>[]`

@returns`CanActivateChildFn[]`

## [Description](#description)

Maps an array of injectable classes with canActivateChild functions to an array of equivalent [`CanActivateChildFn`](/api/router/CanActivateChildFn) for use in a [`Route`](/api/router/Route) definition.

Usage

```
@Injectable({providedIn: 'root'})
export class AdminGuard {
  canActivate() {
    return true;
  }
}

const route: Route = {
  path: 'admin',
  canActivate: mapToCanActivate([AdminGuard]),
};
```

Jump to details

----
url: https://angular.dev/ecosystem/custom-build-pipeline
----

When building an Angular app we strongly recommend you to use the Angular CLI to leverage its structure-dependent update functionality and build system abstraction. This way your projects benefit from the latest security, performance, and API improvements and transparent build improvements.

This page explores the **rare use cases** when you need a custom build pipeline that does not use the Angular CLI. All listed tools below are open source build plugins that are maintained by members of the Angular community. To learn more about their support model and maintenance status look at their documentation and GitHub repository URLs.

## [When should you use a custom build pipeline?](#when-should-you-use-a-custom-build-pipeline)

There are some niche use cases when you may want to maintain a custom build pipeline. For example:

* You have an existing app using a different toolchain and you’d like to add Angular to it
* You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation)
* You’d like to create a short-lived experiment using your favorite build tool

## [What are the options?](#what-are-the-options)

Currently, there are two well supported community tools that enable you to create a custom build pipeline with a [Vite plugin](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) and [Rspack plugin](https://www.npmjs.com/package/@nx/angular-rspack). Both of them use underlying abstractions that power the Angular CLI. They allow you to create a flexible build pipeline and require manual maintenance and no automated update experience.

### [Rspack](#rspack)

Rspack is a Rust-based bundler that aims to provide compatibility with the webpack plugin ecosystem.

If your project is tightly coupled to the webpack ecosystem, heavily relying on a custom webpack configuration you can leverage Rspack to improve your build times.

You can find more about Angular Rspack on the project’s [documentation website](https://nx.dev/recipes/angular/rspack/introduction).

### [Vite](#vite)

Vite is a frontend build tool that aims to provide a faster and leaner development experience for modern web projects. Vite is also extensible through its plugin system that allows ecosystems to build integrations with Vite, such as Vitest for unit and browser testing, Storybook for authoring components in isolation, and more. The Angular CLI also uses Vite as its development server.

The [AnalogJS Vite plugin for Angular](https://www.npmjs.com/package/@analogjs/vite-plugin-angular) enables the adoption of Angular with a project or framework that uses or is built on top of Vite. This can consist of developing and building an Angular project with Vite directly, or adding Angular to an existing project or pipeline. One example is integrating Angular UI components into a documentation website using [Astro and Starlight](https://analogjs.org/docs/packages/astro-angular/overview).

You can learn more about AnalogJS and how to use the plugin through its [documentation page](https://analogjs.org/docs/packages/vite-plugin-angular/overview).

----
url: https://angular.dev/tools/cli/schematics-for-libraries
----

When you create an Angular library, you can provide and package it with schematics that integrate it with the Angular CLI. With your schematics, your users can use `ng add` to install an initial version of your library, `ng generate` to create artifacts defined in your library, and `ng update` to adjust their project for a new version of your library that introduces breaking changes.

All three types of schematics can be part of a collection that you package with your library.

## [Creating a schematics collection](#creating-a-schematics-collection)

To start a collection, you need to create the schematic files. The following steps show you how to add initial support without modifying any project files.

1. In your library's root folder, create a `schematics` folder.

2. In the `schematics/` folder, create an `ng-add` folder for your first schematic.

3. At the root level of the `schematics` folder, create a `collection.json` file.

4. Edit the `collection.json` file to define the initial schema for your collection.

   ### projects/my-lib/schematics/collection.json (Schematics Collection)

   ```
   {
     "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
     "schematics": {
       "ng-add": {
         "description": "Add my library to the project.",
         "factory": "./ng-add/index#ngAdd",
         "schema": "./ng-add/schema.json"
       }
     }
   }
   ```

   * The `$schema` path is relative to the Angular Devkit collection schema.
   * The `schematics` object describes the named schematics that are part of this collection.
   * The first entry is for a schematic named `ng-add`. It contains the description, and points to the factory function that is called when your schematic is executed.

5. In your library project's `package.json` file, add a "schematics" entry with the path to your schema file. The Angular CLI uses this entry to find named schematics in your collection when it runs commands.

### projects/my-lib/package.json (Schematics Collection Reference)

```
{
  "name": "my-lib",
  "version": "0.0.1",
  "schematics": "./schematics/collection.json",
}
```

The initial schema that you have created tells the CLI where to find the schematic that supports the `ng add` command. Now you are ready to create that schematic.

## [Providing installation support](#providing-installation-support)

A schematic for the `ng add` command can enhance the initial installation process for your users. The following steps define this type of schematic.

1. Go to the `<lib-root>/schematics/ng-add` folder.

2. Create a `schema.json` file to define the options that the schematic accepts.

   ### projects/my-lib/schematics/ng-add/schema.json (ng-add Schema)

   ```
   {
     "$schema": "https://json-schema.org/schema",
     "$id": "SchematicsMyLibNgAdd",
     "title": "MyLib ng add Schema",
     "type": "object",
     "properties": {
       "project": {
         "type": "string",
         "description": "Name of the project.",
         "$default": {
           "$source": "projectName"
         }
       }
     }
   }
   ```

3. Create a `schema.ts` file to define an interface for the options defined in the `schema.json` file.

   ### projects/my-lib/schematics/ng-add/schema.ts (ng-add Schema Interface)

   ```
   export interface Schema {
     // Name of the project.
     project: string;
   }
   ```

4. Create the main file, `index.ts`, and add the source code for your schematic factory function.

   ### projects/my-lib/schematics/ng-add/index.ts (ng-add Rule Factory)

   ```
   import {Rule} from '@angular-devkit/schematics';
   import {addRootImport} from '@schematics/angular/utility';
   import {Schema} from './schema';

   export function ngAdd(options: Schema): Rule {
     // Add an import `MyLibModule` from `my-lib` to the root of the user's project.
     return addRootImport(
       options.project,
       ({code, external}) => code`${external('MyLibModule', 'my-lib')}`,
     );
   }
   ```

The Angular CLI will install the latest version of the library automatically, and this example is taking it a step further by adding the `MyLibModule` to the root of the application. The `addRootImport` function accepts a callback that needs to return a code block. You can write any code inside of the string tagged with the `code` function and any external symbol have to be wrapped with the `external` function to ensure that the appropriate import statements are generated.

### [Define dependency type](#define-dependency-type)

Use the `save` option of `ng-add` to configure if the library should be added to the `dependencies`, the `devDependencies`, or not saved at all in the project's `package.json` configuration file.

### projects/my-lib/package.json (ng-add Reference)

```
"ng-add": {
  "save": "devDependencies"
},
```

Possible values are:

| Values              | Details                                 |
| :------------------ | :-------------------------------------- |
| `false`             | Don't add the package to `package.json` |
| `true`              | Add the package to the dependencies     |
| `"dependencies"`    | Add the package to the dependencies     |
| `"devDependencies"` | Add the package to the devDependencies  |

## [Building your schematics](#building-your-schematics)

To bundle your schematics together with your library, you must configure the library to build the schematics separately, then add them to the bundle. You must build your schematics *after* you build your library, so they are placed in the correct directory.

* Your library needs a custom TypeScript configuration file with instructions on how to compile your schematics into your distributed library
* To add the schematics to the library bundle, add scripts to the library's `package.json` file

Assume you have a library project `my-lib` in your Angular workspace. To tell the library how to build the schematics, add a `tsconfig.schematics.json` file next to the generated `tsconfig.lib.json` file that configures the library build.

1. Edit the `tsconfig.schematics.json` file to add the following content.

   ### projects/my-lib/tsconfig.schematics.json (TypeScript Config)

   ```
   {
     "compilerOptions": {
       "baseUrl": ".",
       "lib": ["es2018", "dom"],
       "declaration": true,
       "module": "commonjs",
       "moduleResolution": "node",
       "noEmitOnError": true,
       "noFallthroughCasesInSwitch": true,
       "noImplicitAny": true,
       "noImplicitThis": true,
       "noUnusedParameters": true,
       "noUnusedLocals": true,
       "rootDir": "schematics",
       "outDir": "../../dist/my-lib/schematics",
       "skipDefaultLibCheck": true,
       "skipLibCheck": true,
       "sourceMap": true,
       "target": "es6",
       "types": ["jasmine", "node"]
     },
     "include": ["schematics/**/*"],
     "exclude": ["schematics/*/files/**/*"]
   }
   ```

   | Options   | Details                                                                                                          |
   | :-------- | :--------------------------------------------------------------------------------------------------------------- |
   | `rootDir` | Specifies that your `schematics` folder contains the input files to be compiled.                                 |
   | `outDir`  | Maps to the library's output folder. By default, this is the `dist/my-lib` folder at the root of your workspace. |

2. To make sure your schematics source files get compiled into the library bundle, add the following scripts to the `package.json` file in your library project's root folder (`projects/my-lib`).

   ### projects/my-lib/package.json (Build Scripts)

   ```
   {
     "name": "my-lib",
     "version": "0.0.1",
     "scripts": {
       "build": "tsc -p tsconfig.schematics.json",
       "postbuild": "copyfiles schematics/*/schema.json schematics/*/files/** schematics/collection.json ../../dist/my-lib/"
     },
     "peerDependencies": {
       "@angular/common": "^22.0.0",
       "@angular/core": "^22.0.0"
     },
     "schematics": "./schematics/collection.json",
     "ng-add": {
       "save": "devDependencies"
     },
     "devDependencies": {
       "copyfiles": "file:../../node_modules/copyfiles",
       "typescript": "file:../../node_modules/typescript"
     }
   }
   ```

   * The `build` script compiles your schematic using the custom `tsconfig.schematics.json` file
   * The `postbuild` script copies the schematic files after the `build` script completes
   * Both the `build` and the `postbuild` scripts require the `copyfiles` and `typescript` dependencies. To install the dependencies, navigate to the path defined in `devDependencies` and run `npm install` before you run the scripts.

## [Providing generation support](#providing-generation-support)

You can add a named schematic to your collection that lets your users use the `ng generate` command to create an artifact that is defined in your library.

We'll assume that your library defines a service, `my-service`, that requires some setup. You want your users to be able to generate it using the following CLI command.

```
ng generate my-lib:my-service
```

To begin, create a new subfolder, `my-service`, in the `schematics` folder.

### [Configure the new schematic](#configure-the-new-schematic)

When you add a schematic to the collection, you have to point to it in the collection's schema, and provide configuration files to define options that a user can pass to the command.

1. Edit the `schematics/collection.json` file to point to the new schematic subfolder, and include a pointer to a schema file that specifies inputs for the new schematic.

   ### projects/my-lib/schematics/collection.json (Schematics Collection)

   ```
   {
     "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json",
     "schematics": {
       "ng-add": {
         "description": "Add my library to the project.",
         "factory": "./ng-add/index#ngAdd",
         "schema": "./ng-add/schema.json"
       },
       "my-service": {
         "description": "Generate a service in the project.",
         "factory": "./my-service/index#myService",
         "schema": "./my-service/schema.json"
       }
     }
   }
   ```

2. Go to the `<lib-root>/schematics/my-service` folder.

3. Create a `schema.json` file and define the available options for the schematic.

   ### projects/my-lib/schematics/my-service/schema.json (Schematic JSON Schema)

   ```
   {
     "$schema": "https://json-schema.org/schema",
     "$id": "SchematicsMyService",
     "title": "My Service Schema",
     "type": "object",
     "properties": {
       "name": {
         "description": "The name of the service.",
         "type": "string"
       },
       "path": {
         "type": "string",
         "format": "path",
         "description": "The path to create the service.",
         "visible": false,
         "$default": {
           "$source": "workingDirectory"
         }
       },
       "project": {
         "type": "string",
         "description": "The name of the project.",
         "$default": {
           "$source": "projectName"
         }
       }
     },
     "required": ["name"]
   }
   ```

   * *id*: A unique ID for the schema in the collection.
   * *title*: A human-readable description of the schema.
   * *type*: A descriptor for the type provided by the properties.
   * *properties*: An object that defines the available options for the schematic.

   Each option associates key with a type, description, and optional alias. The type defines the shape of the value you expect, and the description is displayed when the user requests usage help for your schematic.

   See the workspace schema for additional customizations for schematic options.

4. Create a `schema.ts` file and define an interface that stores the values of the options defined in the `schema.json` file.

   ### projects/my-lib/schematics/my-service/schema.ts (Schematic Interface)

   ```
   export interface Schema {
     // The name of the service.
     name: string;

     // The path to create the service.
     path?: string;

     // The name of the project.
     project?: string;
   }
   ```

   | Options | Details                                                                                                                                     |
   | :------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
   | name    | The name you want to provide for the created service.                                                                                       |
   | path    | Overrides the path provided to the schematic. The default path value is based on the current working directory.                             |
   | project | Provides a specific project to run the schematic on. In the schematic, you can provide a default if the option is not provided by the user. |

### [Add template files](#add-template-files)

To add artifacts to a project, your schematic needs its own template files. Schematic templates support special syntax to execute code and variable substitution.

1. Create a `files/` folder inside the `schematics/my-service/` folder.

2. Create a file named `__name@dasherize__.service.ts.template` that defines a template to use for generating files. This template will generate a service that already has Angular's [`HttpClient`](/api/common/http/HttpClient) injected into an `http` property.

   ### projects/my-lib/schematics/my-service/files/\_\_name\@dasherize\_\_.service.ts.template (Schematic Template)

   ```
   import { Service } from '@angular/core';
   import { HttpClient } from '@angular/common/http';

   @Service()
   export class <%= classify(name) %>Service {
      private http = inject(HttpClient);
   }
   ```

   * The `classify` and `dasherize` methods are utility functions that your schematic uses to transform your source template and filename.
   * The `name` is provided as a property from your factory function. It is the same `name` you defined in the schema.

### [Add the factory function](#add-the-factory-function)

Now that you have the infrastructure in place, you can define the main function that performs the modifications you need in the user's project.

The Schematics framework provides a file templating system, which supports both path and content templates. The system operates on placeholders defined inside files or paths that loaded in the input [`Tree`](/api/aria/tree/Tree). It fills these in using values passed into the `Rule`.

For details of these data structures and syntax, see the [Schematics README](https://github.com/angular/angular-cli/blob/main/packages/angular_devkit/schematics/README.md).

1. Create the main file `index.ts` and add the source code for your schematic factory function.

2. First, import the schematics definitions you will need. The Schematics framework offers many utility functions to create and use rules when running a schematic.

   ### projects/my-lib/schematics/my-service/index.ts (Imports)

   ```
   import {
     Rule,
     Tree,
     SchematicsException,
     apply,
     url,
     applyTemplates,
     move,
     chain,
     mergeWith,
   } from '@angular-devkit/schematics';

   import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';
   ```

3. Import the defined schema interface that provides the type information for your schematic's options.

   ### projects/my-lib/schematics/my-service/index.ts (Schema Import)

   ```
   import {
     Rule,
     Tree,
     SchematicsException,
     apply,
     url,
     applyTemplates,
     move,
     chain,
     mergeWith,
   } from '@angular-devkit/schematics';

   import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';

   import {Schema as MyServiceSchema} from './schema';
   ```

4. To build up the generation schematic, start with an empty rule factory.

   ### projects/my-lib/schematics/my-service/index.ts (Initial Rule)

   ```
   export function myService(options: MyServiceSchema): Rule {
     return (tree: Tree) => tree;
   }
   ```

This rule factory returns the tree without modification. The options are the option values passed through from the `ng generate` command.

## [Define a generation rule](#define-a-generation-rule)

You now have the framework in place for creating the code that actually modifies the user's application to set it up for the service defined in your library.

The Angular workspace where the user installed your library contains multiple projects (applications and libraries). The user can specify the project on the command line, or let it default. In either case, your code needs to identify the specific project to which this schematic is being applied, so that you can retrieve information from the project configuration.

Do this using the [`Tree`](/api/aria/tree/Tree) object that is passed in to the factory function. The [`Tree`](/api/aria/tree/Tree) methods give you access to the complete file tree in your workspace, letting you read and write files during the execution of the schematic.

### [Get the project configuration](#get-the-project-configuration)

1. To determine the destination project, use the `workspaces.readWorkspace` method to read the contents of the workspace configuration file, `angular.json`. To use `workspaces.readWorkspace` you need to create a `workspaces.WorkspaceHost` from the [`Tree`](/api/aria/tree/Tree). Add the following code to your factory function.

   ### projects/my-lib/schematics/my-service/index.ts (Schema Import)

   ```
   import {
     Rule,
     Tree,
     SchematicsException,
     apply,
     url,
     applyTemplates,
     move,
     chain,
     mergeWith,
   } from '@angular-devkit/schematics';

   import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';

   import {Schema as MyServiceSchema} from './schema';

   function createHost(tree: Tree): workspaces.WorkspaceHost {
     return {
       async readFile(path: string): Promise<string> {
         const data = tree.read(path);
         if (!data) {
           throw new SchematicsException('File not found.');
         }
         return virtualFs.fileBufferToString(data);
       },
       async writeFile(path: string, data: string): Promise<void> {
         return tree.overwrite(path, data);
       },
       async isDirectory(path: string): Promise<boolean> {
         return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;
       },
       async isFile(path: string): Promise<boolean> {
         return tree.exists(path);
       },
     };
   }

   export function myService(options: MyServiceSchema): Rule {
     return async (tree: Tree) => {
       const host = createHost(tree);
       const {workspace} = await workspaces.readWorkspace('/', host);

     };
   }
   ```

   Be sure to check that the context exists and throw the appropriate error.

2. Now that you have the project name, use it to retrieve the project-specific configuration information.

   ### projects/my-lib/schematics/my-service/index.ts (Project)

   ```
   const project = options.project != null ? workspace.projects.get(options.project) : null;
   if (!project) {
     throw new SchematicsException(`Invalid project name: ${options.project}`);
   }

   const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';
   ```

   The `workspace.projects` object contains all the project-specific configuration information.

3. The `options.path` determines where the schematic template files are moved to once the schematic is applied.

   The `path` option in the schematic's schema is substituted by default with the current working directory. If the `path` is not defined, use the `sourceRoot` from the project configuration along with the `projectType`.

   ### projects/my-lib/schematics/my-service/index.ts (Project Info)

   ```
   if (options.path === undefined) {
     options.path = `${project.sourceRoot}/${projectType}`;
   }
   ```

### [Define the rule](#define-the-rule)

A `Rule` can use external template files, transform them, and return another `Rule` object with the transformed template. Use the templating to generate any custom files required for your schematic.

1. Add the following code to your factory function.

   ### projects/my-lib/schematics/my-service/index.ts (Template transform)

   ```
   const templateSource = apply(url('./files'), [
     applyTemplates({
       classify: strings.classify,
       dasherize: strings.dasherize,
       name: options.name,
     }),
     move(normalize(options.path as string)),
   ]);
   ```

   | Methods                               | Details                                                                                                                                                                                                                                          |
   | :------------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
   | [`apply()`](/api/forms/signals/apply) | Applies multiple rules to a source and returns the transformed source. It takes 2 arguments, a source and an array of rules.                                                                                                                     |
   | `url()`                               | Reads source files from your filesystem, relative to the schematic.                                                                                                                                                                              |
   | `applyTemplates()`                    | Receives an argument of methods and properties you want make available to the schematic template and the schematic filenames. It returns a `Rule`. This is where you define the `classify()` and `dasherize()` methods, and the `name` property. |
   | `classify()`                          | Takes a value and returns the value in title case. For example, if the provided name is `my service`, it is returned as `MyService`.                                                                                                             |
   | `dasherize()`                         | Takes a value and returns the value in dashed and lowercase. For example, if the provided name is MyService, it is returned as `my-service`.                                                                                                     |
   | `move()`                              | Moves the provided source files to their destination when the schematic is applied.                                                                                                                                                              |

2. Finally, the rule factory must return a rule.

   ### projects/my-lib/schematics/my-service/index.ts (Chain Rule)

   ```
   return chain([mergeWith(templateSource)]);
   ```

   The `chain()` method lets you combine multiple rules into a single rule, so that you can perform multiple operations in a single schematic. Here you are only merging the template rules with any code executed by the schematic.

See a complete example of the following schematic rule function.

### projects/my-lib/schematics/my-service/index.ts

```
import {
  Rule,
  Tree,
  SchematicsException,
  apply,
  url,
  applyTemplates,
  move,
  chain,
  mergeWith,
} from '@angular-devkit/schematics';

import {strings, normalize, virtualFs, workspaces} from '@angular-devkit/core';

import {Schema as MyServiceSchema} from './schema';

function createHost(tree: Tree): workspaces.WorkspaceHost {
  return {
    async readFile(path: string): Promise<string> {
      const data = tree.read(path);
      if (!data) {
        throw new SchematicsException('File not found.');
      }
      return virtualFs.fileBufferToString(data);
    },
    async writeFile(path: string, data: string): Promise<void> {
      return tree.overwrite(path, data);
    },
    async isDirectory(path: string): Promise<boolean> {
      return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;
    },
    async isFile(path: string): Promise<boolean> {
      return tree.exists(path);
    },
  };
}

export function myService(options: MyServiceSchema): Rule {
  return async (tree: Tree) => {
    const host = createHost(tree);
    const {workspace} = await workspaces.readWorkspace('/', host);


    const project = options.project != null ? workspace.projects.get(options.project) : null;
    if (!project) {
      throw new SchematicsException(`Invalid project name: ${options.project}`);
    }

    const projectType = project.extensions.projectType === 'application' ? 'app' : 'lib';

    if (options.path === undefined) {
      options.path = `${project.sourceRoot}/${projectType}`;
    }

    const templateSource = apply(url('./files'), [
      applyTemplates({
        classify: strings.classify,
        dasherize: strings.dasherize,
        name: options.name,
      }),
      move(normalize(options.path as string)),
    ]);

    return chain([mergeWith(templateSource)]);
  };
}
```

For more information about rules and utility methods, see [Provided Rules](https://github.com/angular/angular-cli/tree/main/packages/angular_devkit/schematics#provided-rules).

## [Running your library schematic](#running-your-library-schematic)

After you build your library and schematics, you can install the schematics collection to run against your project. The following steps show you how to generate a service using the schematic you created earlier.

### [Build your library and schematics](#build-your-library-and-schematics)

From the root of your workspace, run the `ng build` command for your library.

```
ng build my-lib
```

Then, you change into your library directory to build the schematic

```
cd projects/my-lib
npm run build
```

### [Link the library](#link-the-library)

Your library and schematics are packaged and placed in the `dist/my-lib` folder at the root of your workspace. For running the schematic, you need to link the library into your `node_modules` folder. From the root of your workspace, run the `npm link` command with the path to your distributable library.

```
npm link dist/my-lib
```

### [Run the schematic](#run-the-schematic)

Now that your library is installed, run the schematic using the `ng generate` command.

```
ng generate my-lib:my-service --name my-data
```

In the console, you see that the schematic was run and the `my-data.service.ts` file was created in your application folder.

```
CREATE src/app/my-data.service.ts (208 bytes)
```

----
url: https://angular.dev/api/core/EnvironmentProviders
----

```
    
      type EnvironmentProviders = {  ɵbrand: 'EnvironmentProviders';}
    
    
```

## [Description](#description)

Encapsulated [`Provider`](/api/core/Provider)s that are only accepted during creation of an [`EnvironmentInjector`](/api/core/EnvironmentInjector) (e.g. in an [`NgModule`](/api/core/NgModule)).

Using this wrapper type prevents providers which are only designed to work in application/environment injectors from being accidentally included in [`@Component.providers`](/api/core/Component#providers) and ending up in a component injector.

This wrapper type prevents access to the [`Provider`](/api/core/Provider)s inside.

Jump to details

----
url: https://angular.dev/api/core/ContentChildren
----

```
@ContentChildren(
  selector: ProviderToken<unknown> | Function | string,
  opts?: {
    descendants?: boolean;
    emitDistinctChangesOnly?: boolean;
    read?: any;
  },
);
```

## [Description](#description)

Property decorator that configures a content query.

Use to get the [`QueryList`](/api/core/QueryList) of elements or directives from the content DOM. Any time a child element is added, removed, or moved, the query list will be updated, and the changes observable of the query list will emit a new value.

Content queries are set before the `ngAfterContentInit` callback is called.

Does not retrieve elements or directives that are in other components' templates, since a component's template is always a black box to its ancestors.

**Metadata Properties**:

* **selector** - The directive type or the name used for querying.
* **descendants** - If `true` include all descendants of the element. If `false` then only query direct children of the element.
* **emitDistinctChangesOnly** - The `QueryList#changes` observable will emit new values only if the QueryList result has changed. When `false` the `changes` observable might emit even if the QueryList has not changed. **Note:** This config option is **deprecated**, it will be permanently set to `true` and removed in future versions of Angular.
* **read** - Used to read a different token from the queried elements.

The following selectors are supported.

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* A template reference variable as a string (e.g. query `<my-component #cmp></my-component>` with `@ContentChildren('cmp')`)
* Any provider defined in the child component tree of the current component (e.g. `@ContentChildren(SomeService) someService: SomeService`)
* Any provider defined through a string token (e.g. `@ContentChildren('someToken') someTokenVal: any`)
* A [`TemplateRef`](/api/core/TemplateRef) (e.g. query `<ng-template></ng-template>` with `@ContentChildren(TemplateRef) template;`)

In addition, multiple string selectors can be separated with a comma (e.g. `@ContentChildren('cmp1,cmp2')`)

The following values are supported by `read`:

* Any class with the [`@Component`](/api/core/Component) or [`@Directive`](/api/core/Directive) decorator
* Any provider defined on the injector of the component that is matched by the `selector` of this query
* Any provider defined through a string token (e.g. `{provide: 'token', useValue: 'val'}`)
* [`TemplateRef`](/api/core/TemplateRef), [`ElementRef`](/api/core/ElementRef), [`ViewContainerRef`](/api/core/ViewContainerRef), and [`Injector`](/api/core/Injector)

## [Usage Notes](#usage-notes)

Here is a simple demonstration of how the [`ContentChildren`](/api/core/ContentChildren) decorator can be used.

```
import {AfterContentInit, ContentChildren, Directive, QueryList} from '@angular/core';

@Directive({
  selector: 'child-directive',
})
class ChildDirective {}

@Directive({
  selector: 'someDir',
})
class SomeDir implements AfterContentInit {
  @ContentChildren(ChildDirective) contentChildren!: QueryList<ChildDirective>;

  ngAfterContentInit() {
    // contentChildren is set
  }
}
```

### [Tab-pane example](#tab-pane-example)

Here is a slightly more realistic example that shows how [`ContentChildren`](/api/core/ContentChildren) decorators can be used to implement a tab pane component.

```
import {
  ChangeDetectionStrategy,
  Component,
  ContentChildren,
  Directive,
  input,
  QueryList,
  signal,
} from '@angular/core';

@Directive({
  selector: 'pane',
})
export class Pane {
  id = input.required<string>();
}

@Component({
  selector: 'tab',
  template: `
    <div class="top-level">Top level panes: {{ serializedPanes }}</div>
    <div class="nested">Arbitrary nested panes: {{ serializedNestedPanes }}</div>
  `,
  changeDetection: ChangeDetectionStrategy.Eager,
})
export class Tab {
  @ContentChildren(Pane) topLevelPanes!: QueryList<Pane>;
  @ContentChildren(Pane, {descendants: true}) arbitraryNestedPanes!: QueryList<Pane>;

  get serializedPanes(): string {
    return this.topLevelPanes ? this.topLevelPanes.map((p) => p.id()).join(', ') : '';
  }
  get serializedNestedPanes(): string {
    return this.arbitraryNestedPanes ? this.arbitraryNestedPanes.map((p) => p.id()).join(', ') : '';
  }
}

@Component({
  selector: 'example-app',
  imports: [Tab, Pane],
  template: `
    <tab>
      <pane id="1"></pane>
      <pane id="2"></pane>
      @if (shouldShow()) {
        <pane id="3">
          <tab>
            <pane id="3_1"></pane>
            <pane id="3_2"></pane>
          </tab>
        </pane>
      }
    </tab>

    <button (click)="show()">Show 3</button>
  `,
  changeDetection: ChangeDetectionStrategy.Eager,
})
export class ContentChildrenComp {
  shouldShow = signal(false);

  show() {
    this.shouldShow.set(true);
  }
}
```

Jump to details

----
url: https://angular.dev/api/core/testing/discardPeriodicTasks
----

```
function discardPeriodicTasks(): void;
```

@returns`void`

## [Description](#description)

**IMPORTANT:** This API requires Zone.js and cannot be used with the Vitest test runner

Discard all remaining periodic tasks.

Jump to details

----
url: https://angular.dev/api/forms/Validators
----

```
    
      class Validators {
}
    
    
```

Validator that requires the control's value to be greater than or equal to the provided number.

@parammin`number`

@returns`ValidatorFn`

A validator function that returns an error map with the `min` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate against a minimum of 3](#validate-against-a-minimum-of-3)

```
const control = new FormControl(2, Validators.min(3));

console.log(control.errors); // {min: {min: 3, actual: 2}}
```

Validator that requires the control's value to be less than or equal to the provided number.

@parammax`number`

@returns`ValidatorFn`

A validator function that returns an error map with the `max` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate against a maximum of 15](#validate-against-a-maximum-of-15)

```
const control = new FormControl(16, Validators.max(15));

console.log(control.errors); // {max: {max: 15, actual: 16}}
```

Validator that requires the control have a non-empty value.

@paramcontrol`AbstractControl<any, any, any>`

@returns`ValidationErrors | null`

An error map with the `required` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate that the field is non-empty](#validate-that-the-field-is-non-empty)

```
const control = new FormControl('', Validators.required);

console.log(control.errors); // {required: true}
```

Validator that requires the control's value be true. This validator is commonly used for required checkboxes.

@paramcontrol`AbstractControl<any, any, any>`

@returns`ValidationErrors | null`

An error map that contains the `required` property set to `true` if the validation check fails, otherwise `null`.

Usage notes

### [Validate that the field value is true](#validate-that-the-field-value-is-true)

```
const control = new FormControl('some value', Validators.requiredTrue);

console.log(control.errors); // {required: true}
```

Validator that requires the control's value pass an email validation test.

Tests the value using a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) pattern suitable for common use cases. The pattern is based on the definition of a valid email address in the [WHATWG HTML specification](https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address) with some enhancements to incorporate more RFC rules (such as rules related to domain names and the lengths of different parts of the address).

The differences from the WHATWG version include:

* Disallow `local-part` (the part before the `@` symbol) to begin or end with a period (`.`).
* Disallow `local-part` to be longer than 64 characters.
* Disallow the whole address to be longer than 254 characters.

If this pattern does not satisfy your business needs, you can use [`Validators.pattern()`](/api/forms/Validators#pattern) to validate the value against a different pattern.

@paramcontrol`AbstractControl<any, any, any>`

@returns`ValidationErrors | null`

An error map with the `email` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate that the field matches a valid email pattern](#validate-that-the-field-matches-a-valid-email-pattern)

```
const control = new FormControl('bad@', Validators.email);

console.log(control.errors); // {email: true}
```

Validator that requires the number of items in the control's value to be greater than or equal to the provided minimum length. This validator is also provided by default if you use the HTML5 `minlength` attribute. Note that the `minLength` validator is intended to be used only for types that have a numeric `length` or `size` property, such as strings, arrays or sets. The `minLength` validator logic is also not invoked for values when their `length` or `size` property is 0 (for example in case of an empty string or an empty array), to support optional controls. You can use the standard `required` validator if empty values should not be considered valid.

@paramminLength`number`

@returns`ValidatorFn`

A validator function that returns an error map with the `minlength` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate that the field has a minimum of 3 characters](#validate-that-the-field-has-a-minimum-of-3-characters)

```
const control = new FormControl('ng', Validators.minLength(3));

console.log(control.errors); // {minlength: {requiredLength: 3, actualLength: 2}}
```

```
<input minlength="5">
```

Validator that requires the number of items in the control's value to be less than or equal to the provided maximum length. This validator is also provided by default if you use the HTML5 `maxlength` attribute. Note that the `maxLength` validator is intended to be used only for types that have a numeric `length` or `size` property, such as strings, arrays or sets.

@parammaxLength`number`

@returns`ValidatorFn`

A validator function that returns an error map with the `maxlength` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate that the field has maximum of 5 characters](#validate-that-the-field-has-maximum-of-5-characters)

```
const control = new FormControl('Angular', Validators.maxLength(5));

console.log(control.errors); // {maxlength: {requiredLength: 5, actualLength: 7}}
```

```
<input maxlength="5">
```

Validator that requires the control's value to match a regex pattern. This validator is also provided by default if you use the HTML5 `pattern` attribute.

@parampattern`string | RegExp`

A regular expression to be used as is to test the values, or a string. If a string is passed, the `^` character is prepended and the `$` character is appended to the provided string (if not already present), and the resulting regular expression is used to test the values.

@returns`ValidatorFn`

A validator function that returns an error map with the `pattern` property if the validation check fails, otherwise `null`.

Usage notes

### [Validate that the field only contains letters or spaces](#validate-that-the-field-only-contains-letters-or-spaces)

```
const control = new FormControl('1', Validators.pattern('[a-zA-Z ]*'));

console.log(control.errors); // {pattern: {requiredPattern: '^[a-zA-Z ]*$', actualValue: '1'}}
```

```
<input pattern="[a-zA-Z ]*">
```

### [Pattern matching with the global or sticky flag](#pattern-matching-with-the-global-or-sticky-flag)

`RegExp` objects created with the `g` or `y` flags that are passed into [`Validators.pattern`](/api/forms/Validators#pattern) can produce different results on the same input when validations are run consecutively. This is due to how the behavior of `RegExp.prototype.test` is specified in [ECMA-262](https://tc39.es/ecma262/#sec-regexpbuiltinexec) (`RegExp` preserves the index of the last match when the global or sticky flag is used). Due to this behavior, it is recommended that when using [`Validators.pattern`](/api/forms/Validators#pattern) you **do not** pass in a `RegExp` object with either the global or sticky flag enabled.

```
// Not recommended (since the `g` flag is used)
const controlOne = new FormControl('1', Validators.pattern(/foo/g));

// Good
const controlTwo = new FormControl('1', Validators.pattern(/foo/));
```

Validator that performs no operation.

@paramcontrol`AbstractControl<any, any, any>`

@returns`ValidationErrors | null`

Compose multiple validators into a single function that returns the union of the individual error maps for the provided control.

@paramvalidators`null`

@returns`null`

A validator function that returns an error map with the merged error maps of the validators if the validation check fails, otherwise `null`.

@paramvalidators`(ValidatorFn | null | undefined)[]`

@returns`ValidatorFn | null`

Compose multiple async validators into a single function that returns the union of the individual error objects for the provided control.

@paramvalidators`(AsyncValidatorFn | null)[]`

@returns`AsyncValidatorFn | null`

A validator function that returns an error map with the merged error objects of the async validators if the validation check fails, otherwise `null`.

## [Description](#description)

Provides a set of built-in validators that can be used by form controls.

A validator is a function that processes a [`FormControl`](/api/forms/FormControl) or collection of controls and returns an error map or null. A null map means that validation has passed.

Jump to details

----
url: https://angular.dev/api/service-worker/VersionEvent
----

```
    
      type VersionEvent = | VersionDetectedEvent  | VersionInstallationFailedEvent  | VersionReadyEvent  | NoNewVersionDetectedEvent
    
    
```

Jump to details

----
url: https://angular.dev/guide/elements
----

*Angular elements* are Angular components packaged as *custom elements* (also called Web Components), a web standard for defining new HTML elements in a framework-agnostic way.

[Custom elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements) are a Web Platform feature available on all browsers supported by Angular. A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code. The browser maintains a `CustomElementRegistry` of defined custom elements, which maps an instantiable JavaScript class to an HTML tag.

The `@angular/elements` package exports a [`createCustomElement()`](/api/elements/createCustomElement) API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API.

Transforming a component to a custom element makes all the required Angular infrastructure available to the browser. Creating a custom element is simple and straightforward, and automatically connects your component-defined view with change detection and data binding, mapping Angular functionality to the corresponding built-in HTML equivalents.

## [Using custom elements](#using-custom-elements)

Custom elements bootstrap themselves - they start when they are added to the DOM, and are destroyed when removed from the DOM. Once a custom element is added to the DOM for any page, it looks and behaves like any other HTML element, and does not require any special knowledge of Angular terms or usage conventions.

To add the `@angular/elements` package to your workspace, run the following command:

### npm

```
npm install @angular/elements
```

### yarn

```
yarn add @angular/elements
```

### pnpm

```
pnpm add @angular/elements
```

### bun

```
bun add @angular/elements
```

### [How it works](#how-it-works)

The [`createCustomElement()`](/api/elements/createCustomElement) function converts a component into a class that can be registered with the browser as a custom element. After you register your configured class with the browser's custom-element registry, use the new element just like a built-in HTML element in content that you add directly into the DOM:

```
<my-popup message="Use Angular!" />
```

When your custom element is placed on a page, the browser creates an instance of the registered class and adds it to the DOM. The content is provided by the component's template, which uses Angular template syntax, and is rendered using the component and DOM data. Input properties in the component correspond to input attributes for the element.

## [Transforming components to custom elements](#transforming-components-to-custom-elements)

Angular provides the [`createCustomElement()`](/api/elements/createCustomElement) function for converting an Angular component, together with its dependencies, to a custom element.

The conversion process implements the [`NgElementConstructor`](/api/elements/NgElementConstructor) interface, and creates a constructor class that is configured to produce a self-bootstrapping instance of your component.

Use the browser's native [`customElements.define()`](https://developer.mozilla.org/docs/Web/API/CustomElementRegistry/define) function to register the configured constructor and its associated custom-element tag with the browser's [`CustomElementRegistry`](https://developer.mozilla.org/docs/Web/API/CustomElementRegistry). When the browser encounters the tag for the registered element, it uses the constructor to create a custom-element instance.

**IMPORTANT:** Avoid using the component's selector as the custom element tag name. This can lead to unexpected behavior, due to Angular creating two component instances for a single DOM element: One regular Angular component and a second one using the custom element.

### [Mapping](#mapping)

A custom element *hosts* an Angular component, providing a bridge between the data and logic defined in the component and standard DOM APIs. Component properties and logic map directly into HTML attributes and the browser's event system.

* The creation API parses the component looking for input properties, and defines corresponding attributes for the custom element. It transforms the property names to make them compatible with custom elements, which do not recognize case distinctions. The resulting attribute names use dash-separated lowercase. For example, for a component with `inputProp = input('', {alias: 'myInputProp'})`, the corresponding custom element defines an attribute `my-input-prop`.

* Component outputs are dispatched as HTML [Custom Events](https://developer.mozilla.org/docs/Web/API/CustomEvent), with the name of the custom event matching the output name. For example, for a component with `valueChanged = output()`, the corresponding custom element dispatches events with the name "valueChanged", and the emitted data is stored on the event's `detail` property. If you provide an alias, that value is used; for example, `clicks = output<string>({alias: 'myClick'});` results in dispatch events with the name "myClick".

For more information, see Web Component documentation for [Creating custom events](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Events#creating_custom_events).

## [Example: A Popup Service](#example-a-popup-service)

To add a component to an application at runtime, you can [render it programmatically](guide/components/programmatic-rendering) with the [`createComponent`](/api/core/createComponent) API. With this approach, you are responsible for the surrounding infrastructure: attaching the component's host view to the [`ApplicationRef`](/api/core/ApplicationRef) so that change detection runs, setting its inputs, subscribing to its outputs, and detaching and cleaning up the view when the component is removed.

Using an Angular custom element makes the process simpler and more transparent, by providing all of this infrastructure automatically — all you have to do is define the kind of event handling you want.

The following Popup Service example application defines a component that you can either load dynamically or convert to a custom element.

| Files              | Details                                                                                                                                                                                                             |
| :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `popup.ts`         | Defines a simple pop-up element that displays an input message, with some animation and styling.                                                                                                                    |
| `popup.service.ts` | Creates an injectable service that provides two different ways to invoke the `Popup`; as a dynamic component, or as a custom element. Notice how much more setup is required for the dynamic-loading method.        |
| `app.ts`           | Defines the application's root component, which uses the `PopupService` to add the pop-up to the DOM at run time. When the application runs, the root component's constructor converts `Popup` to a custom element. |

For comparison, the demo shows both methods. One button adds the popup using the dynamic-loading method, and the other uses the custom element. The result is the same, but the preparation is different.

### popup.ts

```
import {Component, computed, input, output} from '@angular/core';
import {animate, state, style, transition, trigger} from '@angular/animations';

@Component({
  selector: 'my-popup',
  template: `
    <span>Popup: {{ message() }}</span>
    <button type="button" (click)="closed.emit()">&#x2716;</button>
  `,
  animations: [
    trigger('state', [
      state('opened', style({transform: 'translateY(0%)'})),
      state('void, closed', style({transform: 'translateY(100%)', opacity: 0})),
      transition('* => *', animate('100ms ease-in')),
    ]),
  ],
  styles: [
    `
      :host {
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        background: #009cff;
        height: 48px;
        padding: 16px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        border-top: 1px solid black;
        font-size: 24px;
      }

      button {
        border-radius: 50%;
      }
    `,
  ],
  host: {
    '[@state]': 'state()',
  },
})
export class Popup {
  readonly message = input('');
  readonly closed = output<void>();

  readonly state = computed(() => (this.message() ? 'opened' : 'closed'));
}
```

### popup.service.ts

```
import {
  ApplicationRef,
  createComponent,
  EnvironmentInjector,
  inject,
  Injectable,
} from '@angular/core';
import {NgElement, WithProperties} from '@angular/elements';
import {Popup} from './popup';

@Injectable()
export class PopupService {
  private readonly injector = inject(EnvironmentInjector);
  private readonly applicationRef = inject(ApplicationRef);

  // Previous dynamic-loading method required you to set up infrastructure
  // before adding the popup to the DOM.
  showAsComponent(message: string) {
    // Create element
    const popup = document.createElement('popup-component');

    // Create the component and wire it up with the element
    const popupComponentRef = createComponent(Popup, {
      environmentInjector: this.injector,
      hostElement: popup,
    });

    // Attach to the view so that the change detector knows to run
    this.applicationRef.attachView(popupComponentRef.hostView);

    // Listen to the close event
    popupComponentRef.instance.closed.subscribe(() => {
      document.body.removeChild(popup);
      this.applicationRef.detachView(popupComponentRef.hostView);
    });

    // Set the message
    popupComponentRef.setInput('message', message);

    // Add to the DOM
    document.body.appendChild(popup);
  }

  // This uses the new custom-element method to add the popup to the DOM.
  showAsElement(message: string) {
    // Create element
    const popupEl: NgElement & WithProperties<Popup> = document.createElement(
      'popup-element',
    ) as any;

    // Listen to the close event
    popupEl.addEventListener('closed', () => document.body.removeChild(popupEl));

    // Set the message
    popupEl.setAttribute('message', message);
    // Add to the DOM
    document.body.appendChild(popupEl);
  }
}
```

### app.ts

```
import {Component, Injector} from '@angular/core';
import {createCustomElement} from '@angular/elements';
import {Popup} from './popup';
import {PopupService} from './popup.service';

@Component({
  selector: 'app-root',
  template: `
    <input #input value="Message" />
    <button type="button" (click)="popup.showAsComponent(input.value)">Show as component</button>
    <button type="button" (click)="popup.showAsElement(input.value)">Show as element</button>
  `,
  providers: [PopupService],
  imports: [Popup],
})
export class App {
  constructor(
    injector: Injector,
    public popup: PopupService,
  ) {
    // Convert `PopupComponent` to a custom element.
    const PopupElement = createCustomElement(Popup, {injector});
    // Register the custom element with the browser.
    customElements.define('popup-element', PopupElement);
  }
}
```

## [Typings for custom elements](#typings-for-custom-elements)

Generic DOM APIs, such as `document.createElement()` or `document.querySelector()`, return an element type that is appropriate for the specified arguments. For example, calling `document.createElement('a')` returns an `HTMLAnchorElement`, which TypeScript knows has an `href` property. Similarly, `document.createElement('div')` returns an `HTMLDivElement`, which TypeScript knows has no `href` property.

When called with unknown elements, such as a custom element name (`popup-element` in our example), the methods return a generic type, such as `HTMLElement`, because TypeScript can't infer the correct type of the returned element.

Custom elements created with Angular extend [`NgElement`](/api/elements/NgElement) (which in turn extends `HTMLElement`). Additionally, these custom elements will have a property for each input of the corresponding component. For example, our `popup-element` has a `message` property of type `string`.

There are a few options if you want to get correct types for your custom elements. Assume you create a `my-dialog` custom element based on the following component:

```
@Component(/* ... */)
class MyDialog {
  content = input('');
}
```

The most straightforward way to get accurate typings is to cast the return value of the relevant DOM methods to the correct type. For that, use the [`NgElement`](/api/elements/NgElement) and [`WithProperties`](/api/elements/WithProperties) types (both exported from `@angular/elements`):

```
const aDialog = document.createElement('my-dialog') as NgElement &
  WithProperties<{content: string}>;
aDialog.content = 'Hello, world!';
aDialog.content = 123; // <-- ERROR: TypeScript knows this should be a string.
aDialog.body = 'News'; // <-- ERROR: TypeScript knows there is no `body` property on `aDialog`.
```

This is a good way to quickly get TypeScript features, such as type checking and autocomplete support, for your custom element. But it can get cumbersome if you need it in several places, because you have to cast the return type on every occurrence.

An alternative way, that only requires defining each custom element's type once, is augmenting the `HTMLElementTagNameMap`, which TypeScript uses to infer the type of a returned element based on its tag name (for DOM methods such as `document.createElement()`, `document.querySelector()`, etc.):

```
declare global {
  interface HTMLElementTagNameMap {
    'my-dialog': NgElement & WithProperties<{content: string}>;
    'my-other-element': NgElement & WithProperties<{foo: 'bar'}>;
    …
  }
}
```

Now, TypeScript can infer the correct type the same way it does for built-in elements:

```
document.createElement('div'); //--> HTMLDivElement (built-in element)
document.querySelector('foo'); //--> Element        (unknown element)
document.createElement('my-dialog'); //--> NgElement & WithProperties<{content: string}> (custom element)
document.querySelector('my-other-element'); //--> NgElement & WithProperties<{foo: 'bar'}>      (custom element)
```

## [Limitations](#limitations)

Care should be taken when destroying and then re-attaching custom elements created with `@angular/elements` due to issues with the [disconnect()](https://github.com/angular/angular/issues/38778) callback. Cases where you may run into this issue are:

* Rendering a component in an `ng-if` or `ng-repeat` in `AngularJS`
* Manually detaching and re-attaching an element to the DOM

----
url: https://angular.dev/api/forms/COMPOSITION_BUFFER_MODE
----

```
    
      const COMPOSITION_BUFFER_MODE: InjectionToken<boolean>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/isFormArray
----

```
    
      const isFormArray: (control: unknown) => control is FormArray<any>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/ng-container
----

A special element that can hold structural directives without adding new elements to the DOM.

The `<ng-container>` allows us to use structural directives without any extra element, making sure that the only DOM changes being applied are those dictated by the directives themselves.

This not only increases performance (even so slightly) since the browser ends up rendering less elements but can also be a valuable asset in having cleaner DOMs and styles alike.

It can for example enable us to use structural directives without breaking styling dependent on a precise DOM structure (as for example the ones we get when using flex containers, margins, the child combinator selector, etc.).

## [Usage notes](#usage-notes)

### [With `*NgIf`s](#with-ngifs)

One common use case of `<ng-container>` is alongside the `*ngIf` structural directive. By using the special element we can produce very clean templates easy to understand and work with.

For example, we may want to have a number of elements shown conditionally but they do not need to be all under the same root element. That can be easily done by wrapping them in such a block:

```
<ng-container *ngIf="condition"> … </ng-container>
```

This can also be augmented with an `else` statement alongside an `<ng-template>` as:

```
<ng-container *ngIf="condition; else templateA"> … </ng-container>
<ng-template #templateA> … </ng-template>
```

### [Combination of multiple structural directives](#combination-of-multiple-structural-directives)

Multiple structural directives cannot be used on the same element; if you need to take advantage of more than one structural directive, it is advised to use an `<ng-container>` per structural directive.

The most common scenario is with `*ngIf` and `*ngFor`. For example, let's imagine that we have a list of items but each item needs to be displayed only if a certain condition is true. We could be tempted to try something like:

```
<ul>
    <li *ngFor="let item of items" *ngIf="item.isValid">
    {{ item.name }}
    </li>
</ul>
```

As we said that would not work, what we can do is to simply move one of the structural directives to an `<ng-container>` element, which would then wrap the other one, like so:

```
<ul>
    <ng-container *ngFor="let item of items">
        <li *ngIf="item.isValid"> {{ item.name }}
        </li>
    </ng-container>
</ul>
```

This would work as intended without introducing any new unnecessary elements in the DOM.

For more information see [one structural directive per element](guide/directives/structural-directives#one-structural-directive-per-element).

### [Use alongside ngTemplateOutlet](#use-alongside-ngtemplateoutlet)

The `NgTemplateOutlet` directive can be applied to any element but most of the time it's applied to `<ng-container>` ones. By combining the two, we get a very clear and easy to follow HTML and DOM structure in which no extra elements are necessary and template views are instantiated where requested.

For example, imagine a situation in which we have a large HTML, in which a small portion needs to be repeated in different places. A simple solution is to define an `<ng-template>` containing our repeating HTML and render that where necessary by using `<ng-container>` alongside an `NgTemplateOutlet`.

Like so:

```
<ng-container *ngTemplateOutlet="tmpl; context: {$implicit: 'Hello'}" />

<ng-container *ngTemplateOutlet="tmpl; context: {$implicit: 'World'}" />

<ng-template #tmpl let-text>
    <h1>
    {{ text }}
    </h1>
</ng-template>
```

For more information regarding `NgTemplateOutlet`, see the [`NgTemplateOutlet`s api documentation page](api/common/NgTemplateOutlet).

Jump to details

----
url: https://angular.dev/api/forms/FormControlStatus
----

## [API](/api/forms/FormControlStatus#api)

```
    
      type FormControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED'
    
    
```

## [Description](/api/forms/FormControlStatus#description)

A form can have several different statuses. Each possible status is returned as a string literal.

* **VALID**: Reports that a control is valid, meaning that no errors exist in the input value.
* **INVALID**: Reports that a control is invalid, meaning that an error exists in the input value.
* **PENDING**: Reports that a control is pending, meaning that async validation is occurring and errors are not yet available for the input value.
* **DISABLED**: Reports that a control is disabled, meaning that the control is exempt from ancestor calculations of validity or value.

Jump to details

----
url: https://angular.dev/api/forms/signals/validate
----

```
function validate<TValue, TPathKind extends PathKind = PathKind.Root>(
  path: SchemaPath<TValue, 1, TPathKind>,
  logic: NoInfer<FieldValidator<TValue, TPathKind>>,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

The target path to add the validation logic to.

@paramlogic`NoInfer<FieldValidator<TValue, TPathKind>>`

A [`Validator`](/api/forms/signals/Validator) that returns the current validation errors.

@returns`void`

Jump to details

----
url: https://angular.dev/cli/generate/app-shell
----

```
ng generate app-shell [options]
```

Configures your project to generate an app-shell during build time.

## [Options](#options)

`project`

The name of the project where the app-shell should be generated.

Value Type`string`

Jump to details

----
url: https://angular.dev/cli/cache/enable
----

```
ng cache enable
```

```
ng cache on
```

Enables disk cache for all projects in the workspace.

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/17-reactive-forms
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/16-form-control-values)[](/tutorials/learn-angular/18-forms-validation)

[](/tutorials/learn-angular/16-form-control-values)[](/tutorials/learn-angular/18-forms-validation)

When you want to manage your forms programmatically instead of relying purely on the template, reactive forms are the answer.

**NOTE:** Learn more about [reactive forms in the in-depth guide](/guide/forms/reactive-forms).

In this activity, you'll learn how to set up reactive forms.

***

1. ### [Import `ReactiveForms` module](#import-reactiveforms-module)

   In `app.ts`, import [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule) from `@angular/forms` and add it to the `imports` array of the component.

   ```
   import { ReactiveFormsModule } from '@angular/forms';

   @Component({
     selector: 'app-root',
     template: `
       <form>
         <label>Name
           <input type="text" />
         </label>
         <label>Email
           <input type="email" />
         </label>
         <button type="submit">Submit</button>
       </form>
     `,
     imports: [ReactiveFormsModule],
   })
   ```

2. ### [Create the `FormGroup` object with FormControls](#create-the-formgroup-object-with-formcontrols)

   Reactive forms use the [`FormControl`](/api/forms/FormControl) class to represent the form controls (e.g., inputs). Angular provides the [`FormGroup`](/api/forms/FormGroup) class to serve as a grouping of form controls into a helpful object that makes handling large forms more convenient for developers.

   Add [`FormControl`](/api/forms/FormControl) and [`FormGroup`](/api/forms/FormGroup) to the import from `@angular/forms` so that you can create a FormGroup for each form, with the properties `name` and `email` as FormControls.

   ```
   import {ReactiveFormsModule, FormControl, FormGroup } from '@angular/forms';
   ...
   export class App {
     profileForm = new FormGroup({
       name: new FormControl(''),
       email: new FormControl(''),
     });
   }
   ```

3. ### [Link the FormGroup and FormControls to the form](#link-the-formgroup-and-formcontrols-to-the-form)

   Each [`FormGroup`](/api/forms/FormGroup) should be attached to a form using the `[formGroup]` directive.

   In addition, each [`FormControl`](/api/forms/FormControl) can be attached with the [`formControlName`](/api/forms/SelectMultipleControlValueAccessor) directive and assigned to the corresponding property. Update the template with the following form code:

   ```
   <form [formGroup]="profileForm">
     <label>
       Name
       <input type="text" formControlName="name" />
     </label>
     <label>
       Email
       <input type="email" formControlName="email" />
     </label>
     <button type="submit">Submit</button>
   </form>
   ```

4. ### [Handle update to the form](#handle-update-to-the-form)

   When you want to access data from the [`FormGroup`](/api/forms/FormGroup), it can be done by accessing the value of the [`FormGroup`](/api/forms/FormGroup). Update the `template` to display the form values:

   ```
   ...
   <h2>Profile Form</h2>
   <p>Name: {{ profileForm.value.name }}</p>
   <p>Email: {{ profileForm.value.email }}</p>
   ```

5. ### [Access FormGroup values](#access-formgroup-values)

   Add a new method to the component class called `handleSubmit` that you'll later use to handle the form submission. This method will display values from the form, you can access the values from the FormGroup.

   In the component class, add the `handleSubmit()` method to handle the form submission.

   ```
   handleSubmit() {
     alert(
       this.profileForm.value.name + ' | ' + this.profileForm.value.email
     );
   }
   ```

6. ### [Add `ngSubmit` to the form](#add-ngsubmit-to-the-form)

   You have access to the form values, now it is time to handle the submission event and use the `handleSubmit` method. Angular has an event handler for this specific purpose called `ngSubmit`. Update the form element to call the `handleSubmit` method when the form is submitted.

   ```
   <form [formGroup]="profileForm" (ngSubmit)="handleSubmit()"></form>
   ```

And just like that, you know how to work with reactive forms in Angular.

Fantastic job with this activity. Keep going to learn about form validation.

----
url: https://angular.dev/api/common/http/HttpFeature
----

## [API](/api/common/http/HttpFeature#api)

```
    
      interface HttpFeature<KindT extends HttpFeatureKind> {}
    
    
```

Jump to details

----
url: https://angular.dev/errors/NG0951
----

Required child query ([`contentChild.required`](/api/core/contentChild) or [`viewChild.required`](/api/core/viewChild)) result was accessed before query results were calculated or query has no matches.

This can happen in two distinct situations:

* query results were accessed before a given query could collect results;
* a query was executed but didn't match any nodes and has no results as a consequence.

Content queries and view queries each calculate their results at different points in time:

* [`contentChild`](/api/core/contentChild) results are available after a *host* view (template where a directive declaring a query is used) is created;
* [`viewChild`](/api/core/viewChild) results are available after a template of a component declaring a query is created.

Accessing query results before they're available results in the error described on this page. Most notably, query results are *never* available in a constructor of the component or directive declaring a query.

## [Fixing the error](#fixing-the-error)

[`contentChild`](/api/core/contentChild) query results can be accessed in the [`AfterContentChecked`](/api/core/AfterContentChecked) lifecycle hook, or later. [`viewChild`](/api/core/viewChild) query results can be accessed in the [`AfterViewChecked`](/api/core/AfterViewChecked) lifecycle hook, or later.

Make sure that a required query matches at least one node and has results at all. You can verify this by accessing query results in the lifecycle hooks listed above.

----
url: https://angular.dev/api/common/NgTemplateOutlet
----

## [API](/api/common/NgTemplateOutlet#api)

```
    
      class NgTemplateOutlet<C = unknown> implements OnChanges {}
    
    
```

@param\_viewContainerRef`ViewContainerRef`

@returns`NgTemplateOutlet<C>`

A context object to attach to the [`EmbeddedViewRef`](/api/core/EmbeddedViewRef). This should be an object, the object's keys will be available for binding by the local template `let` declarations. Using the key `$implicit` in the context object will set its value as default.

A string defining the template reference and optionally the context object for the template.

Injector to be used within the embedded view. A value of "outlet" can be used to indicate that the injector should be inherited from the template outlet's location in the instantiated DOM.

@paramchanges`{ [propName: string]: SimpleChange<any>; }`

@returns`void`

## [Description](/api/common/NgTemplateOutlet#description)

Inserts an embedded view from a prepared [`TemplateRef`](/api/core/TemplateRef).

You can attach a context object to the [`EmbeddedViewRef`](/api/core/EmbeddedViewRef) by setting `[ngTemplateOutletContext]`. `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding by the local template `let` declarations.

## [Exported by](/api/common/NgTemplateOutlet#exported-by)

* `CommonModule`

## [Usage Notes](/api/common/NgTemplateOutlet#usage-notes)

```
<ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
```

Using the key `$implicit` in the context object will set its value as default.

### [Example](/api/common/NgTemplateOutlet#example)

```
@Component({
  selector: 'ng-template-outlet-example',
  imports: [NgTemplateOutlet],
  template: `
    <ng-container *ngTemplateOutlet="greet"></ng-container>
    <hr />
    <ng-container *ngTemplateOutlet="eng; context: myContext"></ng-container>
    <hr />
    <ng-container *ngTemplateOutlet="svk; context: myContext"></ng-container>
    <hr />

    <ng-template #greet><span>Hello</span></ng-template>
    <ng-template #eng let-name
      ><span>Hello {{ name }}!</span></ng-template
    >
    <ng-template #svk let-person="localSk"
      ><span>Ahoj {{ person }}!</span></ng-template
    >
  `,
})
export class NgTemplateOutletExample {
  myContext = {$implicit: 'World', localSk: 'Svet'};
}
```

Jump to details

----
url: https://angular.dev/api/router/ViewTransitionInfo
----

```
    
      interface ViewTransitionInfo {}
    
    
```

The `ViewTransition` returned by the call to `startViewTransition`.

The [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) that the navigation is transitioning from.

The [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) that the navigation is transitioning to.

Jump to details

----
url: https://angular.dev/api/platform-browser/provideCssVarNamespacing
----

```
function provideCssVarNamespacing(
  namespace?: string | undefined,
): EnvironmentProviders;
```

@paramnamespace`string | undefined`

The prefix string to use as a namespace. If not provided, it defaults to the [`APP_ID`](/api/core/APP_ID). An underscore is appended unconditionally.

@returns`EnvironmentProviders`

Jump to details

----
url: https://angular.dev/api/forms/signals/applyWhenValue
----

```
function applyWhenValue<TValue, TNarrowed extends TValue>(
  path: SchemaPath<TValue>,
  predicate: (value: TValue) => value is TNarrowed,
  schema: SchemaOrSchemaFn<TNarrowed>,
): void;
function applyWhenValue<TValue>(
  path: SchemaPath<TValue>,
  predicate: (value: TValue) => boolean,
  schema: NoInfer<SchemaOrSchemaFn<TValue>>,
): void;
```

Conditionally applies a predefined schema to a given `FieldPath`.

@parampath`SchemaPath<TValue>`

The target path to apply the schema to.

@parampredicate`(value: TValue) => value is TNarrowed`

A type guard that accepts a value `T` and returns `true` if `T` is of type `TNarrowed`.

@paramschema`SchemaOrSchemaFn<TNarrowed>`

The schema to apply to the field when `predicate` returns `true`.

@returns`void`

Conditionally applies a predefined schema to a given `FieldPath`.

@parampath`SchemaPath<TValue>`

The target path to apply the schema to.

@parampredicate`(value: TValue) => boolean`

A function that accepts a value `T` and returns `true` when the schema should be applied.

@paramschema`NoInfer<SchemaOrSchemaFn<TValue>>`

The schema to apply to the field when `predicate` returns `true`.

@returns`void`

Jump to details

----
url: https://angular.dev/api/ssr/withAppShell
----

```
function withAppShell(
  component:
    | Type<unknown>
    | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>),
): ServerRenderingFeature<ServerRenderingFeatureKind.AppShell>;
```

@paramcomponent`Type<unknown> | (() => Promise<Type<unknown> | DefaultExport<Type<unknown>>>)`

* The Angular component to render for the app shell. Can be a direct component type or a dynamic import function.

@returns`ServerRenderingFeature<ServerRenderingFeatureKind.AppShell>`

A `ServerRenderingFeature` object configuring the app shell.

## [Description](#description)

Configures the shell of the application.

The app shell is a minimal, static HTML page that is served immediately, while the full Angular application loads in the background. This improves perceived performance by providing instant feedback to the user.

This function configures the app shell route, which serves the provided component for requests that do not match any defined server routes.

## [Usage Notes](#usage-notes)

```
import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';
import { AppShellComponent } from './app-shell.component';

provideServerRendering(
  withRoutes(serverRoutes),
  withAppShell(AppShellComponent)
);
```

```
import { provideServerRendering, withAppShell, withRoutes } from '@angular/ssr';

provideServerRendering(
  withRoutes(serverRoutes),
  withAppShell(() =>
    import('./app-shell.component').then((m) => m.AppShellComponent)
  )
);
```

Jump to details

----
url: https://angular.dev/api/core/HostBinding
----

```
@HostBinding(hostPropertyName?: string);
```

## [Usage Notes](#usage-notes)

**NOTE:** **Always** prefer using the [`host` property](guide/components/host-elements#binding-to-the-host-element) over [`@HostBinding`](/api/core/HostBinding). This decorator exist exclusively for backwards compatibility.

The following example creates a directive that sets the `valid` and `invalid` class, a style color, and an id on the DOM element that has an `ngModel` directive on it.

```
@Directive({selector: '[ngModel]'})
class NgModelStatus {
  constructor(public control: NgModel) {}
  // class bindings
  @HostBinding('class.valid') get valid() { return this.control.valid; }
  @HostBinding('class.invalid') get invalid() { return this.control.invalid; }

  // style binding
  @HostBinding('style.color') get color() { return this.control.valid ? 'green': 'red'; }

  // style binding also supports a style unit extension
  @HostBinding('style.width.px') @Input() width: number = 500;

  // attribute binding
  @HostBinding('attr.aria-required')
  @Input() required: boolean = false;

  // property binding
  @HostBinding('id') get id() { return this.control.value?.length ? 'odd':  'even'; }

@Component({
  selector: 'app',
  template: `<input [(ngModel)]="prop">`,
})
class App {
  prop;
}
```

Jump to details

----
url: https://angular.dev/api/ssr/node/CommonEngineRenderOptions
----

```
    
      interface CommonEngineRenderOptions {}
    
    
```

A method that when invoked returns a promise that returns an [`ApplicationRef`](/api/core/ApplicationRef) instance once resolved or an NgModule.

A set of platform level providers for the current request.

Reduce render blocking requests by inlining critical CSS. Defaults to true.

Base path location of index file. Defaults to the 'documentFilePath' dirname when not provided.

Jump to details

----
url: https://angular.dev/api/common/Location
----

```
    
      class Location implements OnDestroy {}
    
    
```

@paramlocationStrategy`LocationStrategy`

@returns`Location`

Normalizes the URL path for this location.

@paramincludeHash`boolean`

True to include an anchor fragment in the path.

@returns`string`

The normalized URL path.

Reports the current state of the location history.

@returns`unknown`

The current value of the `history.state` object.

Normalizes the given path and compares to the current normalized path.

@parampath`string`

The given URL path.

@paramquery`string`

Query parameters.

@returns`boolean`

True if the given URL path is equal to the current normalized path, false otherwise.

Normalizes a URL path by stripping any trailing slashes.

@paramurl`string`

String representing a URL.

@returns`string`

The normalized URL string.

Normalizes an external URL path. If the given URL doesn't begin with a leading slash (`'/'`), adds one before normalizing. Adds a hash if [`HashLocationStrategy`](/api/common/HashLocationStrategy) is in use, or the [`APP_BASE_HREF`](/api/common/APP_BASE_HREF) if the [`PathLocationStrategy`](/api/common/PathLocationStrategy) is in use.

@paramurl`string`

String representing a URL.

@returns`string`

A normalized platform-specific URL.

Changes the browser's URL to a normalized version of a given URL, and pushes a new item onto the platform's history.

@parampath`string`

URL path to normalize.

@paramquery`string`

Query parameters.

@paramstate`any`

Location history state.

@returns`void`

Changes the browser's URL to a normalized version of the given URL, and replaces the top item on the platform's history stack.

@parampath`string`

URL path to normalize.

@paramquery`string`

Query parameters.

@paramstate`any`

Location history state.

@returns`void`

Navigates forward in the platform's history.

@returns`void`

Navigates back in the platform's history.

@returns`void`

Navigate to a specific page from session history, identified by its relative position to the current page.

@paramrelativePosition`number`

Position of the target page in the history relative to the current page. A negative value moves backwards, a positive value moves forwards, e.g. `location.historyGo(2)` moves forward two pages and `location.historyGo(-2)` moves back two pages. When we try to go beyond what's stored in the history session, we stay in the current page. Same behaviour occurs when `relativePosition` equals 0.

@returns`void`

Registers a URL change listener. Use to catch updates performed by the Angular framework that are not detectible through "popstate" or "hashchange" events.

@paramfn`(url: string, state: unknown) => void`

The change handler function, which take a URL and a location history state.

@returns`VoidFunction`

A function that, when executed, unregisters a URL change listener.

Subscribes to the platform's `popState` events.

Note: [`Location.go()`](/api/common/Location#go) does not trigger the `popState` event in the browser. Use [`Location.onUrlChange()`](/api/common/Location#onUrlChange) to subscribe to URL changes instead.

@paramonNext`(value: PopStateEvent) => void`

Callback invoked with the event that is triggered when the state history changes.

@paramonThrow`((exception: any) => void) | null | undefined`

Callback invoked with the exception if an error occurs.

@paramonReturn`(() => void) | null | undefined`

Callback invoked when the subscription completes.

@returns`SubscriptionLike`

Subscribed events.

Normalizes URL parameters by prepending with `?` if needed.

Joins two parts of a URL with a slash if needed.

Removes a trailing slash from a URL string if needed. Looks for the first occurrence of either `#`, `?`, or the end of the line as `/` characters and removes the trailing slash if one exists.

## [Description](#description)

A service that applications can use to interact with a browser's URL.

Depending on the [`LocationStrategy`](/api/common/LocationStrategy) used, [`Location`](/api/common/Location) persists to the URL's path or the URL's hash segment.

## [Usage Notes](#usage-notes)

It's better to use the `Router.navigate()` service to trigger route changes. Use [`Location`](/api/common/Location) only if you need to interact with or create normalized URLs outside of routing.

[`Location`](/api/common/Location) is responsible for normalizing the URL against the application's base href. A normalized URL is absolute from the URL host, includes the application's base href, and has no trailing slash:

* `/my/app/user/123` is normalized
* `my/app/user/123` **is not** normalized
* `/my/app/user/123/` **is not** normalized

### [Example](#example)

```
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {Component} from '@angular/core';

@Component({
  selector: 'path-location',
  providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}],
  template: `
    <h1>PathLocationStrategy</h1>
    Current URL is: <code>{{ location.path() }}</code
    ><br />
    Normalize: <code>/foo/bar/</code> is: <code>{{ location.normalize('foo/bar') }}</code
    ><br />
  `,
  standalone: false,
})
export class PathLocationComponent {
  location: Location;
  constructor(location: Location) {
    this.location = location;
  }
}
```

Jump to details

----
url: https://angular.dev/api/platform-server/renderModule
----

```
function renderModule<T>(
  moduleType: Type<T>,
  options: {
    document?: string | Document | undefined;
    url?: string | undefined;
    extraProviders?: StaticProvider[] | undefined;
    allowedHosts?: string[] | undefined;
  },
): Promise<string>;
```

@parammoduleType`Type<T>`

A reference to an NgModule that should be used for bootstrap.

@paramoptions`{ document?: string | Document | undefined; url?: string | undefined; extraProviders?: StaticProvider[] | undefined; allowedHosts?: string[] | undefined; }`

Additional configuration for the render operation:

* `document` - the document of the page to render, either as an HTML string or as a reference to the `document` instance.
* `url` - the URL for the current render request.
* `extraProviders` - set of platform level providers for the current render request.
* `allowedHosts` - the allowed hosts list for host validation in server-side rendering.

@returns`Promise<string>`

Jump to details

----
url: https://angular.dev/api/upgrade/static/downgradeModule
----

```
function downgradeModule<T>(
  moduleOrBootstrapFn:
    Type<T> | ((extraProviders: StaticProvider[]) => Promise<NgModuleRef<T>>),
): string;
function downgradeModule<T>(moduleOrBootstrapFn: NgModuleFactory<T>): string;
```

A helper function for creating an AngularJS module that can bootstrap an Angular module "on-demand" (possibly lazily) when a [`downgraded component`](/api/upgrade/static/downgradeComponent) needs to be instantiated.

*Part of the [upgrade/static](api?query=upgrade/static) library for hybrid upgrade apps that support AOT compilation.*

It allows loading/bootstrapping the Angular part of a hybrid application lazily and not having to pay the cost up-front. For example, you can have an AngularJS application that uses Angular for specific routes and only instantiate the Angular modules if/when the user visits one of these routes.

The Angular module will be bootstrapped once (when requested for the first time) and the same reference will be used from that point onwards.

[`downgradeModule()`](/api/upgrade/static/downgradeModule) requires either an [`NgModuleFactory`](/api/core/NgModuleFactory), [`NgModule`](/api/core/NgModule) class or a function:

* [`NgModuleFactory`](/api/core/NgModuleFactory): If you pass an [`NgModuleFactory`](/api/core/NgModuleFactory), it will be used to instantiate a module using [`platformBrowser`](/api/platform-browser/platformBrowser)'s [`bootstrapModuleFactory()`](/api/core/PlatformRef#bootstrapModuleFactory).

  **NOTE:** this type of the argument is deprecated. Please either provide an [`NgModule`](/api/core/NgModule) class or a bootstrap function instead.

* [`NgModule`](/api/core/NgModule) class: If you pass an NgModule class, it will be used to instantiate a module using [`platformBrowser`](/api/platform-browser/platformBrowser)'s [`bootstrapModule()`](/api/core/PlatformRef#bootstrapModule).

* `Function`: If you pass a function, it is expected to return a promise resolving to an [`NgModuleRef`](/api/core/NgModuleRef). The function is called with an array of extra [`Providers`](/api/core/StaticProvider) that are expected to be available from the returned [`NgModuleRef`](/api/core/NgModuleRef)'s [`Injector`](/api/core/Injector).

[`downgradeModule()`](/api/upgrade/static/downgradeModule) returns the name of the created AngularJS wrapper module. You can use it to declare a dependency in your main AngularJS module.

{@example upgrade/static/ts/lite/module.ts region="basic-how-to"}

For more details on how to use [`downgradeModule()`](/api/upgrade/static/downgradeModule) see [Upgrading for Performance](https://angular.io/guide/upgrade).

@parammoduleOrBootstrapFn`Type<T> | ((extraProviders: StaticProvider[]) => Promise<NgModuleRef<T>>)`

@returns`string`

Usage notes

Apart from [`UpgradeModule`](/api/upgrade/static/UpgradeModule), you can use the rest of the `upgrade/static` helpers as usual to build a hybrid application. Note that the Angular pieces (e.g. downgraded services) will not be available until the downgraded module has been bootstrapped, i.e. by instantiating a downgraded component.

You cannot use [`downgradeModule()`](/api/upgrade/static/downgradeModule) and [`UpgradeModule`](/api/upgrade/static/UpgradeModule) in the same hybrid application.\
Use one or the other.

### [Differences with ``](#differences-with-upgrademodule)`UpgradeModule`

Besides their different API, there are two important internal differences between [`downgradeModule()`](/api/upgrade/static/downgradeModule) and [`UpgradeModule`](/api/upgrade/static/UpgradeModule) that affect the behavior of hybrid applications:

1. Unlike [`UpgradeModule`](/api/upgrade/static/UpgradeModule), [`downgradeModule()`](/api/upgrade/static/downgradeModule) does not bootstrap the main AngularJS module inside the [`Angular zone`](/api/core/NgZone).
2. Unlike [`UpgradeModule`](/api/upgrade/static/UpgradeModule), [`downgradeModule()`](/api/upgrade/static/downgradeModule) does not automatically run a [$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) when changes are detected in the Angular part of the application.

What this means is that applications using [`UpgradeModule`](/api/upgrade/static/UpgradeModule) will run change detection more frequently in order to ensure that both frameworks are properly notified about possible changes. This will inevitably result in more change detection runs than necessary.

[`downgradeModule()`](/api/upgrade/static/downgradeModule), on the other side, does not try to tie the two change detection systems as tightly, restricting the explicit change detection runs only to cases where it knows it is necessary (e.g. when the inputs of a downgraded component change). This improves performance, especially in change-detection-heavy applications, but leaves it up to the developer to manually notify each framework as needed.

For a more detailed discussion of the differences and their implications, see [Upgrading for Performance](https://angular.io/guide/upgrade).

You can manually trigger a change detection run in AngularJS using [scope.$apply(...)](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply) or [$rootScope.$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest).

You can manually trigger a change detection run in Angular using [`* ngZone.run(...)`](/api/core/NgZone#run).

### [Downgrading multiple modules](#downgrading-multiple-modules)

It is possible to downgrade multiple modules and include them in an AngularJS application. In that case, each downgraded module will be bootstrapped when an associated downgraded component or injectable needs to be instantiated.

Things to keep in mind, when downgrading multiple modules:

* Each downgraded component/injectable needs to be explicitly associated with a downgraded module. See [`downgradeComponent()`](/api/upgrade/static/downgradeComponent) and [`downgradeInjectable()`](/api/upgrade/static/downgradeInjectable) for more details.

* If you want some injectables to be shared among all downgraded modules, you can provide them as [`StaticProvider`](/api/core/StaticProvider)s, when creating the [`PlatformRef`](/api/core/PlatformRef) (e.g. via [`platformBrowser`](/api/platform-browser/platformBrowser) or `platformBrowserDynamic`).

* When using [`bootstrapModule()`](/api/core/PlatformRef#bootstrapModule) or [`bootstrapModuleFactory()`](/api/core/PlatformRef#bootstrapModuleFactory) to bootstrap the downgraded modules, each one is considered a "root" module. As a consequence, a new instance will be created for every injectable provided in `"root"` (via [`providedIn`](/api/core/Injectable) If this is not your intention, you can have a shared module (that will act as act as the "root" module) and create all downgraded modules using that module's injector:

  {@example upgrade/static/ts/lite-multi-shared/module.ts region="shared-root-module"}

A helper function for creating an AngularJS module that can bootstrap an Angular module "on-demand" (possibly lazily) when a [`downgraded component`](/api/upgrade/static/downgradeComponent) needs to be instantiated.

*Part of the [upgrade/static](api?query=upgrade/static) library for hybrid upgrade apps that support AOT compilation.*

It allows loading/bootstrapping the Angular part of a hybrid application lazily and not having to pay the cost up-front. For example, you can have an AngularJS application that uses Angular for specific routes and only instantiate the Angular modules if/when the user visits one of these routes.

The Angular module will be bootstrapped once (when requested for the first time) and the same reference will be used from that point onwards.

[`downgradeModule()`](/api/upgrade/static/downgradeModule) requires either an [`NgModuleFactory`](/api/core/NgModuleFactory), [`NgModule`](/api/core/NgModule) class or a function:

* [`NgModuleFactory`](/api/core/NgModuleFactory): If you pass an [`NgModuleFactory`](/api/core/NgModuleFactory), it will be used to instantiate a module using [`platformBrowser`](/api/platform-browser/platformBrowser)'s [`bootstrapModuleFactory()`](/api/core/PlatformRef#bootstrapModuleFactory).

  **NOTE:** this type of the argument is deprecated. Please either provide an [`NgModule`](/api/core/NgModule) class or a bootstrap function instead.

* [`NgModule`](/api/core/NgModule) class: If you pass an NgModule class, it will be used to instantiate a module using [`platformBrowser`](/api/platform-browser/platformBrowser)'s [`bootstrapModule()`](/api/core/PlatformRef#bootstrapModule).

* `Function`: If you pass a function, it is expected to return a promise resolving to an [`NgModuleRef`](/api/core/NgModuleRef). The function is called with an array of extra [`Providers`](/api/core/StaticProvider) that are expected to be available from the returned [`NgModuleRef`](/api/core/NgModuleRef)'s [`Injector`](/api/core/Injector).

[`downgradeModule()`](/api/upgrade/static/downgradeModule) returns the name of the created AngularJS wrapper module. You can use it to declare a dependency in your main AngularJS module.

{@example upgrade/static/ts/lite/module.ts region="basic-how-to"}

For more details on how to use [`downgradeModule()`](/api/upgrade/static/downgradeModule) see [Upgrading for Performance](https://angular.io/guide/upgrade).

@deprecated

Passing [`NgModuleFactory`](/api/core/NgModuleFactory) as the [`downgradeModule`](/api/upgrade/static/downgradeModule) function argument is deprecated, please pass an NgModule class reference instead.

@parammoduleOrBootstrapFn`NgModuleFactory<T>`

@returns`string`

Usage notes

Apart from [`UpgradeModule`](/api/upgrade/static/UpgradeModule), you can use the rest of the `upgrade/static` helpers as usual to build a hybrid application. Note that the Angular pieces (e.g. downgraded services) will not be available until the downgraded module has been bootstrapped, i.e. by instantiating a downgraded component.

You cannot use [`downgradeModule()`](/api/upgrade/static/downgradeModule) and [`UpgradeModule`](/api/upgrade/static/UpgradeModule) in the same hybrid application.\
Use one or the other.

### [Differences with ``](#differences-with-upgrademodule)`UpgradeModule`

Besides their different API, there are two important internal differences between [`downgradeModule()`](/api/upgrade/static/downgradeModule) and [`UpgradeModule`](/api/upgrade/static/UpgradeModule) that affect the behavior of hybrid applications:

1. Unlike [`UpgradeModule`](/api/upgrade/static/UpgradeModule), [`downgradeModule()`](/api/upgrade/static/downgradeModule) does not bootstrap the main AngularJS module inside the [`Angular zone`](/api/core/NgZone).
2. Unlike [`UpgradeModule`](/api/upgrade/static/UpgradeModule), [`downgradeModule()`](/api/upgrade/static/downgradeModule) does not automatically run a [$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) when changes are detected in the Angular part of the application.

What this means is that applications using [`UpgradeModule`](/api/upgrade/static/UpgradeModule) will run change detection more frequently in order to ensure that both frameworks are properly notified about possible changes. This will inevitably result in more change detection runs than necessary.

[`downgradeModule()`](/api/upgrade/static/downgradeModule), on the other side, does not try to tie the two change detection systems as tightly, restricting the explicit change detection runs only to cases where it knows it is necessary (e.g. when the inputs of a downgraded component change). This improves performance, especially in change-detection-heavy applications, but leaves it up to the developer to manually notify each framework as needed.

For a more detailed discussion of the differences and their implications, see [Upgrading for Performance](https://angular.io/guide/upgrade).

You can manually trigger a change detection run in AngularJS using [scope.$apply(...)](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply) or [$rootScope.$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest).

You can manually trigger a change detection run in Angular using [`* ngZone.run(...)`](/api/core/NgZone#run).

### [Downgrading multiple modules](#downgrading-multiple-modules)

It is possible to downgrade multiple modules and include them in an AngularJS application. In that case, each downgraded module will be bootstrapped when an associated downgraded component or injectable needs to be instantiated.

Things to keep in mind, when downgrading multiple modules:

* Each downgraded component/injectable needs to be explicitly associated with a downgraded module. See [`downgradeComponent()`](/api/upgrade/static/downgradeComponent) and [`downgradeInjectable()`](/api/upgrade/static/downgradeInjectable) for more details.

* If you want some injectables to be shared among all downgraded modules, you can provide them as [`StaticProvider`](/api/core/StaticProvider)s, when creating the [`PlatformRef`](/api/core/PlatformRef) (e.g. via [`platformBrowser`](/api/platform-browser/platformBrowser) or `platformBrowserDynamic`).

* When using [`bootstrapModule()`](/api/core/PlatformRef#bootstrapModule) or [`bootstrapModuleFactory()`](/api/core/PlatformRef#bootstrapModuleFactory) to bootstrap the downgraded modules, each one is considered a "root" module. As a consequence, a new instance will be created for every injectable provided in `"root"` (via [`providedIn`](/api/core/Injectable) If this is not your intention, you can have a shared module (that will act as act as the "root" module) and create all downgraded modules using that module's injector:

  {@example upgrade/static/ts/lite-multi-shared/module.ts region="shared-root-module"}

## [Description](#description)

A helper function for creating an AngularJS module that can bootstrap an Angular module "on-demand" (possibly lazily) when a [`downgraded component`](/api/upgrade/static/downgradeComponent) needs to be instantiated.

*Part of the [upgrade/static](api?query=upgrade/static) library for hybrid upgrade apps that support AOT compilation.*

It allows loading/bootstrapping the Angular part of a hybrid application lazily and not having to pay the cost up-front. For example, you can have an AngularJS application that uses Angular for specific routes and only instantiate the Angular modules if/when the user visits one of these routes.

The Angular module will be bootstrapped once (when requested for the first time) and the same reference will be used from that point onwards.

[`downgradeModule()`](/api/upgrade/static/downgradeModule) requires either an [`NgModuleFactory`](/api/core/NgModuleFactory), [`NgModule`](/api/core/NgModule) class or a function:

* [`NgModuleFactory`](/api/core/NgModuleFactory): If you pass an [`NgModuleFactory`](/api/core/NgModuleFactory), it will be used to instantiate a module using [`platformBrowser`](/api/platform-browser/platformBrowser)'s [`bootstrapModuleFactory()`](/api/core/PlatformRef#bootstrapModuleFactory).

  **NOTE:** this type of the argument is deprecated. Please either provide an [`NgModule`](/api/core/NgModule) class or a bootstrap function instead.

* [`NgModule`](/api/core/NgModule) class: If you pass an NgModule class, it will be used to instantiate a module using [`platformBrowser`](/api/platform-browser/platformBrowser)'s [`bootstrapModule()`](/api/core/PlatformRef#bootstrapModule).

* `Function`: If you pass a function, it is expected to return a promise resolving to an [`NgModuleRef`](/api/core/NgModuleRef). The function is called with an array of extra [`Providers`](/api/core/StaticProvider) that are expected to be available from the returned [`NgModuleRef`](/api/core/NgModuleRef)'s [`Injector`](/api/core/Injector).

[`downgradeModule()`](/api/upgrade/static/downgradeModule) returns the name of the created AngularJS wrapper module. You can use it to declare a dependency in your main AngularJS module.

```
import {BrowserModule, platformBrowser} from '@angular/platform-browser';
import {downgradeComponent, downgradeModule, UpgradeComponent} from '@angular/upgrade/static';

// The function that will bootstrap the Angular module (when/if necessary).
// (This would be omitted if we provided an `NgModuleFactory` directly.)
const ng2BootstrapFn = (extraProviders: StaticProvider[]) =>
  platformBrowser(extraProviders).bootstrapModule(MyLazyAngularModule);
// This AngularJS module represents the AngularJS pieces of the application.
const myMainAngularJsModule = angular.module('myMainAngularJsModule', [
  // We declare a dependency on the "downgraded" Angular module.
  downgradeModule(ng2BootstrapFn),
  // or
  // downgradeModule(MyLazyAngularModuleFactory)
]);
```

For more details on how to use [`downgradeModule()`](/api/upgrade/static/downgradeModule) see [Upgrading for Performance](https://angular.io/guide/upgrade).

## [Usage Notes](#usage-notes)

Apart from [`UpgradeModule`](/api/upgrade/static/UpgradeModule), you can use the rest of the `upgrade/static` helpers as usual to build a hybrid application. Note that the Angular pieces (e.g. downgraded services) will not be available until the downgraded module has been bootstrapped, i.e. by instantiating a downgraded component.

You cannot use [`downgradeModule()`](/api/upgrade/static/downgradeModule) and [`UpgradeModule`](/api/upgrade/static/UpgradeModule) in the same hybrid application.\
Use one or the other.

### [Differences with ``](#differences-with-upgrademodule)`UpgradeModule`

Besides their different API, there are two important internal differences between [`downgradeModule()`](/api/upgrade/static/downgradeModule) and [`UpgradeModule`](/api/upgrade/static/UpgradeModule) that affect the behavior of hybrid applications:

1. Unlike [`UpgradeModule`](/api/upgrade/static/UpgradeModule), [`downgradeModule()`](/api/upgrade/static/downgradeModule) does not bootstrap the main AngularJS module inside the [`Angular zone`](/api/core/NgZone).
2. Unlike [`UpgradeModule`](/api/upgrade/static/UpgradeModule), [`downgradeModule()`](/api/upgrade/static/downgradeModule) does not automatically run a [$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest) when changes are detected in the Angular part of the application.

What this means is that applications using [`UpgradeModule`](/api/upgrade/static/UpgradeModule) will run change detection more frequently in order to ensure that both frameworks are properly notified about possible changes. This will inevitably result in more change detection runs than necessary.

[`downgradeModule()`](/api/upgrade/static/downgradeModule), on the other side, does not try to tie the two change detection systems as tightly, restricting the explicit change detection runs only to cases where it knows it is necessary (e.g. when the inputs of a downgraded component change). This improves performance, especially in change-detection-heavy applications, but leaves it up to the developer to manually notify each framework as needed.

For a more detailed discussion of the differences and their implications, see [Upgrading for Performance](https://angular.io/guide/upgrade).

You can manually trigger a change detection run in AngularJS using [scope.$apply(...)](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply) or [$rootScope.$digest()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest).

You can manually trigger a change detection run in Angular using [`* ngZone.run(...)`](/api/core/NgZone#run).

### [Downgrading multiple modules](#downgrading-multiple-modules)

It is possible to downgrade multiple modules and include them in an AngularJS application. In that case, each downgraded module will be bootstrapped when an associated downgraded component or injectable needs to be instantiated.

Things to keep in mind, when downgrading multiple modules:

* Each downgraded component/injectable needs to be explicitly associated with a downgraded module. See [`downgradeComponent()`](/api/upgrade/static/downgradeComponent) and [`downgradeInjectable()`](/api/upgrade/static/downgradeInjectable) for more details.

* If you want some injectables to be shared among all downgraded modules, you can provide them as [`StaticProvider`](/api/core/StaticProvider)s, when creating the [`PlatformRef`](/api/core/PlatformRef) (e.g. via [`platformBrowser`](/api/platform-browser/platformBrowser) or `platformBrowserDynamic`).

* When using [`bootstrapModule()`](/api/core/PlatformRef#bootstrapModule) or [`bootstrapModuleFactory()`](/api/core/PlatformRef#bootstrapModuleFactory) to bootstrap the downgraded modules, each one is considered a "root" module. As a consequence, a new instance will be created for every injectable provided in `"root"` (via [`providedIn`](/api/core/Injectable) If this is not your intention, you can have a shared module (that will act as act as the "root" module) and create all downgraded modules using that module's injector:

```
let rootInjectorPromise: Promise<Injector> | null = null;
const getRootInjector = (extraProviders: StaticProvider[]) => {
  if (!rootInjectorPromise) {
    rootInjectorPromise = platformBrowser(extraProviders)
      .bootstrapModule(Ng2RootModule)
      .then((moduleRef) => moduleRef.injector);
  }
  return rootInjectorPromise;
};

const downgradedNg2AModule = downgradeModule(async (extraProviders: StaticProvider[]) => {
  const rootInjector = await getRootInjector(extraProviders)!;
  const moduleAFactory = await rootInjector.get(Compiler).compileModuleAsync(Ng2AModule);
  return moduleAFactory.create(rootInjector);
});
const downgradedNg2BModule = downgradeModule(async (extraProviders: StaticProvider[]) => {
  const rootInjector = await getRootInjector(extraProviders)!;
  const moduleBFactory = await rootInjector.get(Compiler).compileModuleAsync(Ng2BModule);
  return moduleBFactory.create(rootInjector);
});
const appModule = angular
  .module('exampleAppModule', [downgradedNg2AModule, downgradedNg2BModule, downgradedNg2CModule])
```

Jump to details

----
url: https://angular.dev/api/elements/createCustomElement
----

```
function createCustomElement<P>(
  component: Type<any>,
  config: NgElementConfig,
): NgElementConstructor<P>;
```

@paramcomponent`Type<any>`

The component to transform.

@paramconfig`NgElementConfig`

A configuration that provides initialization information to the created class.

@returns`NgElementConstructor<P>`

The custom-element construction class, which can be registered with a browser's `CustomElementRegistry`.

## [Description](#description)

Creates a custom element class based on an Angular component.

Builds a class that encapsulates the functionality of the provided component and uses the configuration information to provide more context to the class. Takes the component factory's inputs and outputs to convert them to the proper custom element API and add hooks to input changes.

The configuration's injector is the initial injector set on the class, and used by default for each created instance.This behavior can be overridden with the static property to affect all newly created instances, or as a constructor argument for one-off creations.

Jump to details

----
url: https://angular.dev/guide/routing/redirecting-routes
----

Route redirects allow you to automatically navigate users from one route to another. Think of it like mail forwarding, where mail intended for one address is sent to a different address. This is useful for handling legacy URLs, implementing default routes, or managing access control.

## [How to configure redirects](#how-to-configure-redirects)

You can define redirects in your route configuration with the `redirectTo` property. This property accepts a string.

```
import {Routes} from '@angular/router';

const routes: Routes = [
  // Simple redirect
  {path: 'marketing', redirectTo: 'newsletter'},

  // Redirect with path parameters
  {path: 'legacy-user/:id', redirectTo: 'users/:id'},

  // Redirect any other URLs that don’t match
  // (also known as a "wildcard" redirect)
  {path: '**', redirectTo: '/login'},
];
```

In this example, there are three redirects:

1. When a user visits the `/marketing` path, they are redirected to `/newsletter`.
2. When a user visits any `/legacy-user/:id` path, they are routed to the corresponding `/users/:id` path.
3. When a user visit any path that’s not defined in the router, they are redirected to the login page because of the `**` wildcard path definition.

## [Understanding `pathMatch`](#understanding-pathmatch)

The `pathMatch` property on routes enables developers to control how Angular matches a URL to routes.

There are two values that `pathMatch` accepts:

| Value      | Description                                  |
| ---------- | -------------------------------------------- |
| `'full'`   | The entire URL path must match exactly       |
| `'prefix'` | Only the beginning of the URL needs to match |

By default, all redirects use the `prefix` strategy.

### [`pathMatch: 'prefix'`](#pathmatch-prefix)

`pathMatch: 'prefix'` is the default strategy and ideal when you want Angular Router to match all subsequent routes when triggering a redirect.

```
export const routes: Routes = [
  // This redirect route is equivalent to…
  {path: 'news', redirectTo: 'blog'},

  // This explicitly defined route redirect pathMatch
  {path: 'news', redirectTo: 'blog', pathMatch: 'prefix'},
];
```

In this example, all routes that are prefixed with `news` are redirected to their `/blog` equivalents. Here are some examples where users are redirected when visiting the old `news` prefix:

* `/news` redirects to `/blog`
* `/news/article` redirects to `/blog/article`
* `/news/article/:id` redirects to `/blog/article/:id`

### [`pathMatch: 'full'`](#pathmatch-full)

On the other hand, `pathMatch: 'full'` is useful when you want Angular Router to only redirect a specific path.

```
export const routes: Routes = [{path: '', redirectTo: '/dashboard', pathMatch: 'full'}];
```

In this example, any time the user visits the root URL (i.e., `''`), the router redirects that user to the `'/dashboard'` page.

Any subsequent pages (e.g., `/login`, `/about`, `/product/id`, etc.), are ignored and do not trigger a redirect.

**TIP:** Be careful when configuring a redirect on the root page (i.e., `"/"` or `""`). If you do not set `pathMatch: 'full'`, the router will redirect all URLs.

To further illustrate this, if the `news` example from the previous section used `pathMatch: 'full'` instead:

```
export const routes: Routes = [{path: 'news', redirectTo: '/blog', pathMatch: 'full'}];
```

This means that:

1. Only the `/news` path will be redirected to `/blog`.
2. Any subsequent segments such as `/news/articles` or `/news/articles/1` would not redirect with the new `/blog` prefix.

## [Conditional redirects](#conditional-redirects)

The `redirectTo` property can also accept a function in order to add logic to how users are redirected.

The [function](api/router/RedirectFunction) only has access part of the [`ActivatedRouteSnapshot`](api/router/ActivatedRouteSnapshot) data since some data is not accurately known at the route matching phase. Examples include: resolved titles, lazy loaded components, etc.

It typically returns a string or [`URLTree`](api/router/UrlTree), but it can also return an observable or promise.

Here is an example where the user is redirected to different menu based on the time of the day:

```
import {Routes} from '@angular/router';
import {Menu} from './menu';

export const routes: Routes = [
  {
    path: 'restaurant/:location/menu',
    redirectTo: (activatedRouteSnapshot) => {
      const location = activatedRouteSnapshot.params['location'];
      const currentHour = new Date().getHours();

      // Check if user requested a specific meal via query parameter
      if (activatedRouteSnapshot.queryParams['meal']) {
        return `/restaurant/${location}/menu/${activatedRouteSnapshot.queryParams['meal']}`;
      }

      // Auto-redirect based on time of day
      if (currentHour >= 5 && currentHour < 11) {
        return `/restaurant/${location}/menu/breakfast`;
      } else if (currentHour >= 11 && currentHour < 17) {
        return `/restaurant/${location}/menu/lunch`;
      } else {
        return `/restaurant/${location}/menu/dinner`;
      }
    },
  },

  // Destination routes
  {path: 'restaurant/:location/menu/breakfast', component: Menu},
  {path: 'restaurant/:location/menu/lunch', component: Menu},
  {path: 'restaurant/:location/menu/dinner', component: Menu},

  // Default redirect
  {path: '', redirectTo: '/restaurant/downtown/menu', pathMatch: 'full'},
];
```

To learn more, check out [the API docs for the RedirectFunction](api/router/RedirectFunction).

## [Next steps](#next-steps)

For more information about the `redirectTo` property, check out the [API docs](api/router/Route#redirectTo).

----
url: https://angular.dev/reference/migrations/router-testing-module-migration
----

This schematic migrates usages of [`RouterTestingModule`](/api/router/testing/RouterTestingModule) inside tests to [`RouterModule`](/api/router/RouterModule).

When a test imports [`SpyLocation`](/api/common/testing/SpyLocation) from `@angular/common/testing` and uses the `urlChanges` property, the schematic will also add [`provideLocationMocks()`](/api/common/testing/provideLocationMocks) to preserve the original behavior.

Run the schematic with:

```
ng generate @angular/core:router-testing-module-migration
```

## [Options](#options)

| Option | Details                                                                                                                       |
| :----- | :---------------------------------------------------------------------------------------------------------------------------- |
| `path` | The path (relative to project root) to migrate. Defaults to `./`. Use this to incrementally migrate a subset of your project. |

## [Examples](#examples)

### [Preserve router options](#preserve-router-options)

Before:

```
import {RouterTestingModule} from '@angular/router/testing';
import {TestBed} from '@angular/core/testing';
import {SpyLocation} from '@angular/common/testing';

describe('test', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes(routes, {initialNavigation: 'enabledBlocking'})],
    });
  });
});
```

After:

```
import {RouterModule} from '@angular/router';
import {TestBed} from '@angular/core/testing';
import {SpyLocation} from '@angular/common/testing';

describe('test', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterModule.forRoot(routes, {initialNavigation: 'enabledBlocking'})],
    });
  });
});
```

### [Add provideLocationMocks when `SpyLocation` is imported and `urlChanges` is used](#add-providelocationmocks-when-spylocation-is-imported-and-urlchanges-is-used)

Before:

```
import {RouterTestingModule} from '@angular/router/testing';
import {TestBed} from '@angular/core/testing';
import {SpyLocation} from '@angular/common/testing';

describe('test', () => {
  let spy: SpyLocation;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule],
    });
    spy = TestBed.inject(SpyLocation);
  });

  it('Awesome test', () => {
    expect(spy.urlChanges).toBeDefined();
  });
});
```

After:

```
import {RouterModule} from '@angular/router';
import {TestBed} from '@angular/core/testing';
import {SpyLocation, provideLocationMocks} from '@angular/common/testing';

describe('test', () => {
  let spy: SpyLocation;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterModule],
      providers: [provideLocationMocks()],
    });
    spy = TestBed.inject(SpyLocation);
  });

  it('Awesome test', () => {
    expect(spy.urlChanges).toBeDefined();
  });
});
```

----
url: https://angular.dev/tools/libraries
----

Many applications need to solve the same general problems, such as presenting a unified user interface, presenting data, and allowing data entry. Developers can create general solutions for particular domains that can be adapted for re-use in different applications. Such a solution can be built as Angular *libraries* and these libraries can be published and shared as *npm packages*.

An Angular library is an Angular project that differs from an application in that it cannot run on its own. A library must be imported and used in an application.

Libraries extend Angular's base features. For example, to add [reactive forms](guide/forms/reactive-forms) to an application, add the library package using `ng add @angular/forms`, then import the [`ReactiveFormsModule`](/api/forms/ReactiveFormsModule) from the `@angular/forms` library in your application code. Similarly, adding the [service worker](ecosystem/service-workers) library to an Angular application is one of the steps for turning an application into a [Progressive Web App](https://developers.google.com/web/progressive-web-apps) (PWA). [Angular Material](https://material.angular.dev) is an example of a large, general-purpose library that provides sophisticated, reusable, and adaptable UI components.

Any application developer can use these and other libraries that have been published as npm packages by the Angular team or by third parties. See [Using Published Libraries](tools/libraries/using-libraries).

**HELPFUL:** Libraries are intended to be used by Angular applications. To add Angular features to non-Angular web applications, use [Angular custom elements](guide/elements).

## [Creating libraries](#creating-libraries)

If you have developed features that are suitable for reuse, you can create your own libraries. These libraries can be used locally in your workspace, or you can publish them as [npm packages](reference/configs/npm-packages) to share with other projects or other Angular developers. These packages can be published to the npm registry, a private npm Enterprise registry, or a private package management system that supports npm packages. See [Creating Libraries](tools/libraries/creating-libraries).

Deciding to package features as a library is an architectural decision. It is comparable to deciding whether a feature is a component or a service, or deciding on the scope of a component.

Packaging features as a library forces the artifacts in the library to be decoupled from the application's business logic. This can help to avoid various bad practices or architecture mistakes that can make it difficult to decouple and reuse code in the future.

Putting code into a separate library is more complex than simply putting everything in one application. It requires more of an investment in time and thought for managing, maintaining, and updating the library. This complexity can pay off when the library is being used in multiple applications.

----
url: https://angular.dev/api/cdk/drag-drop/CDK_DROP_LIST_GROUP
----

## [API](/api/cdk/drag-drop/CDK_DROP_LIST_GROUP#api)

```
    
      const CDK_DROP_LIST_GROUP: any;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/MAX_DATE
----

```
    
      const MAX_DATE: LimitKey<Date>;
    
    
```

Jump to details

----
url: https://angular.dev/hmr
----

- - [Overview](/tools/cli)
  - [Local set-up](/tools/cli/setup-local)
  - [Building Angular apps](/tools/cli/build)
  - [Serving Angular apps for development](/tools/cli/serve)
  - [Deployment](/tools/cli/deployment)
  - [End-to-End Testing](/tools/cli/end-to-end)
  - [Migrating to new build system](/tools/cli/build-system-migration)
  - [Build environments](/tools/cli/environments)
  - [Angular CLI builders](/tools/cli/cli-builder)
  - [Generating code using schematics](/tools/cli/schematics)
  - [Authoring schematics](/tools/cli/schematics-authoring)
  - [Schematics for libraries](/tools/cli/schematics-for-libraries)
  - [Template type checking](/tools/cli/template-typecheck)
  - [Ahead-of-time (AOT) compilation](/tools/cli/aot-compiler)
  - [AOT metadata errors](/tools/cli/aot-metadata-errors)


```
ng update @angular/cli --name use-application-builder
```

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
...
```

Changing the `builder` field is the only change you will need to make.

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser-esbuild",
...
```

#### [Manual migration to the new `application` builder](#manual-migration-to-the-new-application-builder)

A builder named `application` is also available within the `@angular-devkit/build-angular` package that is present in an Angular CLI generated application. This builder is the default for all new applications created via `ng new`.

The following is what you would typically find in `angular.json` for an application:

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
...
```

Changing the `builder` field is the first change you will need to make.

```
...
"architect": {
  "build": {
    "builder": "@angular-devkit/build-angular:application",
...
```

```
ng build
```

## [Starting the development server](#starting-the-development-server)

The development server will automatically detect the new build system and use it to build the application. To start the development server no changes are necessary to the `dev-server` builder configuration or command line.

```
ng serve
```

```
ng serve --no-hmr
```

```
"serve": {
  "builder": "@angular/build:dev-server",
  "options": {
    "prebundle": {
      "exclude": ["some-dep"]
    }
  },
```

By default, `prebundle` is set to `true` but can be set to `false` to fully disable prebundling. However, excluding specific dependencies is recommended instead since rebuild times will increase with prebundling disabled.

```
"serve": {
  "builder": "@angular/build:dev-server",
  "options": {
    "prebundle": false
  },
```

## [New features](#new-features)

One of the main benefits of the application build system is the improved build and rebuild speed. However, the new application build system also provides additional features not present in the `browser` builder.

**IMPORTANT:** The new features of the `application` builder described here are incompatible with the `karma` test builder by default because it is using the `browser` builder internally. Users can opt-in to use the `application` builder by setting the `builderMode` option to `application` for the `karma` builder. This option is currently in developer preview. If you notice any issues, please report them [here](https://github.com/angular/angular-cli/issues).

### [Build-time value replacement with `define`](#build-time-value-replacement-with-define)

The `define` option allows identifiers present in the code to be replaced with another value at build time. This is similar to the behavior of Webpack's `DefinePlugin` which was previously used with some custom Webpack configurations that used third-party builders. The option can either be used within the `angular.json` configuration file or on the command line. Configuring `define` within `angular.json` is useful for cases where the values are constant and able to be checked in to source control.

Within the configuration file, the option is in the form of an object. The keys of the object represent the identifier to replace and the values of the object represent the corresponding replacement value for the identifier. An example is as follows:

```
"build": {
  "builder": "@angular/build:application",
  "options": {
    ...
    "define": {
        "SOME_NUMBER": "5",
        "ANOTHER": "'this is a string literal, note the extra single quotes'",
        "REFERENCE": "globalThis.someValue.noteTheAbsentSingleQuotes"
    }
  }
}
```

**HELPFUL:** All replacement values are defined as strings within the configuration file. If the replacement is intended to be an actual string literal, it should be enclosed in single quote marks. This allows the flexibility of using any valid JSON type as well as a different identifier as a replacement.

The command line usage is preferred for values that may change per build execution such as the git commit hash or an environment variable. The CLI will merge `--define` values from the command line with `define` values from `angular.json`, including both in a build. Command line usage takes precedence if the same identifier is present for both. For command line usage, the `--define` option uses the format of `IDENTIFIER=VALUE`.

```
ng build --define SOME_NUMBER=5 --define "ANOTHER='these will overwrite existing'"
```

Environment variables can also be selectively included in a build. For non-Windows shells, the quotes around the hash literal can be escaped directly if preferred. This example assumes a bash-like shell but similar behavior is available for other shells as well.

```
export MY_APP_API_HOST="http://example.com"
export API_RETRY=3
ng build --define API_HOST=\'$MY_APP_API_HOST\' --define API_RETRY=$API_RETRY
```

For either usage, TypeScript needs to be aware of the types for the identifiers to prevent type-checking errors during the build. This can be accomplished with an additional type definition file within the application source code (`src/types.d.ts`, for example) with similar content:

```
declare const SOME_NUMBER: number;
declare const ANOTHER: string;
declare const GIT_HASH: string;
declare const API_HOST: string;
declare const API_RETRY: number;
```

```
"build": {
  "builder": "@angular/build:application",
  "options": {
    ...
    "loader": {
      ".svg": "text"
    }
  }
}
```

An SVG file can then be imported:

```
import contents from './some-file.svg';

console.log(contents); // <svg>...</svg>
```

Additionally, TypeScript needs to be aware of the module type for the import to prevent type-checking errors during the build. This can be accomplished with an additional type definition file within the application source code (`src/types.d.ts`, for example) with the following or similar content:

```
declare module '*.svg' {
  const content: string;
  export default content;
}
```

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import contents from './some-file.svg' with {loader: 'text'};
```

The same can be accomplished with an import expression inside an async function.

```
async function loadSvg(): Promise<string> {
  // @ts-expect-error TypeScript cannot provide types based on attributes yet
  return import('./some-file.svg', {with: {loader: 'text'}}).then((m) => m.default);
}
```

For the import expression, the `loader` value must be a string literal to be statically analyzed. A warning will be issued if the value is not a string literal.

The `file` loader is useful when a file will be loaded at runtime through either a `fetch()`, setting to an image elements `src`, or other similar method.

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import imagePath from './image.webp' with {loader: 'file'};

console.log(imagePath); // media/image-ULK2SIIB.webp
```

The `base64` loader is useful when a file needs to be embedded directly into the bundle as an encoded string that can later be used to construct a Data URL.

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import logo from './logo.png' with {loader: 'base64'};

console.log(logo); // "iVBORw0KGgoAAAANSUhEUgAA..."
```

The `dataurl` loader to inline assets as complete Data URLs.

```
// @ts-expect-error TypeScript cannot provide types based on attributes yet
import icon from './icon.svg' with {loader: 'dataurl'};

console.log(icon); // "data:image/svg+xml;..."
```

```
import {verboseLogging} from '#logger';
```

The file can be switched in the `imports` field in `package.json`:

```
{
  ...
  "imports": {
    "#logger": {
      "development": "./src/logging/debug.ts",
      "default": "./src/logging/noop.ts"
    }
  }
}
```

For applications that are also using SSR, browser and server code can be switched by using the `browser` condition:

```
{
  ...
  "imports": {
    "#crashReporter": {
      "browser": "./src/browser-logger.ts",
      "default": "./src/server-logger.ts"
    }
  }
}
```

```
import * as moment from 'moment';

console.log(moment().format());
```

The build will generate a warning to notify you that there is a potential problem. The warning will be similar to:

```
▲ [WARNING] Calling "moment" will crash at run-time because it's an import namespace object, not a function [call-import-namespace]

    src/main.ts:2:12:
      2 │ console.log(moment().format());
        ╵             ~~~~~~

Consider changing "moment" to a default import instead:

    src/main.ts:1:7:
      1 │ import * as moment from 'moment';
        │        ~~~~~~~~~~~
        ╵        moment
```

However, you can avoid the runtime errors and the warning by enabling the `esModuleInterop` TypeScript option for the application and changing the import to the following:

```
import moment from 'moment';

console.log(moment().format());
```

----
url: https://angular.dev/guide/aria/tree
----

A tree displays hierarchical data where items can expand to reveal children or collapse to hide them. Users navigate with arrow keys, expand and collapse nodes, and optionally select items for navigation or data selection scenarios.

### TS

```
import {Component, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: TreeNode[] = [
    {
      name: 'public',
      value: 'public',
      children: [
        {name: 'index.html', value: 'public/index.html'},
        {name: 'favicon.ico', value: 'public/favicon.ico'},
        {name: 'styles.css', value: 'public/styles.css'},
      ],
      expanded: true,
    },
    {
      name: 'src',
      value: 'src',
      children: [
        {
          name: 'app',
          value: 'src/app',
          children: [
            {name: 'app.ts', value: 'src/app/app.ts'},
            {name: 'app.html', value: 'src/app/app.html'},
            {name: 'app.css', value: 'src/app/app.css'},
          ],
          expanded: false,
        },
        {
          name: 'assets',
          value: 'src/assets',
          children: [{name: 'logo.png', value: 'src/assets/logo.png'}],
          expanded: false,
        },
        {
          name: 'environments',
          value: 'src/environments',
          children: [
            {
              name: 'environment.prod.ts',
              value: 'src/environments/environment.prod.ts',
              expanded: false,
            },
            {name: 'environment.ts', value: 'src/environments/environment.ts'},
          ],
          expanded: false,
        },
        {name: 'main.ts', value: 'src/main.ts'},
        {name: 'polyfills.ts', value: 'src/polyfills.ts'},
        {name: 'styles.css', value: 'src/styles.css', disabled: true},
        {name: 'test.ts', value: 'src/test.ts'},
      ],
      expanded: false,
    },
    {name: 'angular.json', value: 'angular.json'},
    {name: 'package.json', value: 'package.json'},
    {name: 'README.md', value: 'README.md'},
  ];

  readonly selected = signal(['angular.json']);
}
```

### HTML

```
<ul ngTree #tree="ngTree" [(value)]="selected" class="basic-tree">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [disabled]="node.disabled"
      [(expanded)]="node.expanded"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true" class="material-symbols-outlined expand-icon" translate="no">{{
        node.children ? 'chevron_right' : ''
      }}</span>
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        node.children ? 'folder' : 'docs'
      }}</span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  user-select: none;
  font-family: var(--inter-font);
}

[ngTree] {
  min-width: 24rem;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.3rem 1rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--quinary-contrast);
}

[ngTreeItem]:focus {
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngTreeItem][aria-selected='true'],
[ngTreeItem][aria-selected='true'] .expand-icon {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

.material-symbols-outlined {
  margin: 0;
  width: 24px;
}

.expand-icon {
  transition: transform 0.2s ease;
}

[ngTreeItem][aria-expanded='true'] .expand-icon {
  transform: rotate(90deg);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

## [Usage](#usage)

Trees work well for displaying hierarchical data where users need to navigate through nested structures.

**Use trees when:**

* Building file system navigation
* Showing folder and document hierarchies
* Creating nested menu structures
* Displaying organization charts
* Browsing hierarchical data
* Implementing site navigation with nested sections

**Avoid trees when:**

* Displaying flat lists (use [Listbox](guide/aria/listbox) instead)
* Showing data tables (use [Grid](guide/aria/grid) instead)
* Creating simple dropdowns (use [Select](guide/aria/select) instead)
* Building breadcrumb navigation (use breadcrumb patterns)

## [Features](#features)

* **Hierarchical navigation** - Nested tree structure with expand and collapse functionality
* **Selection modes** - Single or multi-selection with explicit or follow-focus behavior
* **Selection follows focus** - Optional automatic selection when focus changes
* **Keyboard navigation** - Arrow keys, Home, End, and type-ahead search
* **Expand/collapse** - Right/Left arrows or Enter to toggle parent nodes
* **Disabled items** - Disable specific nodes with focus management
* **Focus modes** - Roving tabindex or activedescendant focus strategies
* **RTL support** - Right-to-left language navigation

## [Examples](#examples)

### [Navigation tree](#navigation-tree)

Use a tree for navigation where clicking items triggers actions rather than selecting them.

### TS

```
import {Component, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  icon: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: TreeNode[] = [
    {
      name: 'Inbox',
      value: 'inbox',
      icon: 'inbox',
    },
    {
      name: 'Sent',
      value: 'sent',
      icon: 'send',
    },
    {
      name: 'Drafts',
      value: 'drafts',
      icon: 'draft',
    },
    {
      name: 'Spam',
      value: 'spam',
      icon: 'report',
    },
    {
      name: 'Trash',
      value: 'trash',
      icon: 'delete',
    },
    {
      name: 'Labels',
      value: 'labels',
      expanded: true,
      icon: 'label',
      children: [
        {name: 'Personal', value: 'folders/personal', icon: 'label'},
        {name: 'Work', value: 'folders/work', icon: 'label'},
        {name: 'Travel', value: 'folders/travel', icon: 'label'},
        {name: 'Receipts', value: 'folders/receipts', icon: 'label'},
      ],
    },
  ];

  readonly selected = signal(['inbox']);
}
```

### HTML

```
<ul ngTree #tree="ngTree" [nav]="true" [(value)]="selected" class="basic-tree">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <a
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [disabled]="node.disabled"
      [selectable]="!node.children"
      [(expanded)]="node.expanded"
      #treeItem="ngTreeItem"
      href="#{{ node.name }}"
      (click)="$event.preventDefault()"
    >
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        node.icon
      }}</span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined expand-icon" translate="no">{{
        node.children ? 'keyboard_arrow_up' : ''
      }}</span>
    </a>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  user-select: none;
  font-family: var(--inter-font);
}

[ngTree] {
  min-width: 24rem;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.3rem 1rem;
  color: var(--primary-contrast);
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--quinary-contrast);
}

[ngTreeItem]:focus {
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngTreeItem][aria-current] {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

.material-symbols-outlined {
  margin: 0;
  width: 24px;
}

.expand-icon {
  transition: transform 0.2s ease;
}

[ngTreeItem][aria-expanded='true'] .expand-icon {
  transform: rotate(180deg);
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

Set `[nav]="true"` to enable navigation mode. This uses `aria-current` to indicate the current page instead of selection.

### [Single selection](#single-selection)

Enable single selection for scenarios where users choose one item from the tree.

### TS

```
import {Component, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: TreeNode[] = [
    {
      name: 'public',
      value: 'public',
      children: [
        {name: 'index.html', value: 'public/index.html'},
        {name: 'favicon.ico', value: 'public/favicon.ico'},
        {name: 'styles.css', value: 'public/styles.css'},
      ],
      expanded: true,
    },
    {
      name: 'src',
      value: 'src',
      children: [
        {
          name: 'app',
          value: 'src/app',
          children: [
            {name: 'app.ts', value: 'src/app/app.ts'},
            {name: 'app.html', value: 'src/app/app.html'},
            {name: 'app.css', value: 'src/app/app.css'},
          ],
          expanded: false,
        },
        {
          name: 'assets',
          value: 'src/assets',
          children: [{name: 'logo.png', value: 'src/assets/logo.png'}],
          expanded: false,
        },
        {
          name: 'environments',
          value: 'src/environments',
          children: [
            {
              name: 'environment.prod.ts',
              value: 'src/environments/environment.prod.ts',
              expanded: false,
            },
            {name: 'environment.ts', value: 'src/environments/environment.ts'},
          ],
          expanded: false,
        },
        {name: 'main.ts', value: 'src/main.ts'},
        {name: 'polyfills.ts', value: 'src/polyfills.ts'},
        {name: 'styles.css', value: 'src/styles.css', disabled: true},
        {name: 'test.ts', value: 'src/test.ts'},
      ],
      expanded: false,
    },
    {name: 'angular.json', value: 'angular.json'},
    {name: 'package.json', value: 'package.json'},
    {name: 'README.md', value: 'README.md'},
  ];

  readonly selected = signal(['angular.json']);
}
```

### HTML

```
<ul ngTree #tree="ngTree" [(value)]="selected" class="basic-tree">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [disabled]="node.disabled"
      [(expanded)]="node.expanded"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true" class="material-symbols-outlined expand-icon" translate="no">{{
        node.children ? 'chevron_right' : ''
      }}</span>
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        node.children ? 'folder' : 'docs'
      }}</span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  user-select: none;
  font-family: var(--inter-font);
}

[ngTree] {
  min-width: 24rem;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.3rem 1rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--quinary-contrast);
}

[ngTreeItem]:focus {
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngTreeItem][aria-selected='true'],
[ngTreeItem][aria-selected='true'] .expand-icon {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

.material-symbols-outlined {
  margin: 0;
  width: 24px;
}

.expand-icon {
  transition: transform 0.2s ease;
}

[ngTreeItem][aria-expanded='true'] .expand-icon {
  transform: rotate(90deg);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

### TS

```
import {Component, computed, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: readonly TreeNode[] = [
    {
      name: 'C:',
      value: 'C:',
      expanded: true,
      children: [
        {
          name: 'Program Files/',
          value: 'C:/Program Files',
          children: [
            {name: 'Common Files', value: 'C:/Program Files/Common Files'},
            {name: 'Internet Explorer', value: 'C:/Program Files/Internet Explorer'},
          ],
          expanded: false,
        },
        {
          name: 'Users/',
          value: 'C:/Users',
          children: [
            {name: 'Default', value: 'C:/Users/Default'},
            {name: 'Public', value: 'C:/Users/Public'},
          ],
          expanded: false,
        },
        {
          name: 'Windows/',
          value: 'C:/Windows',
          children: [
            {name: 'System32', value: 'C:/Windows/System32'},
            {name: 'Web', value: 'C:/Windows/Web'},
          ],
          expanded: false,
        },
        {name: 'pagefile.sys', value: 'C:/pagefile.sys'},
        {name: 'swapfile.sys', value: 'C:/swapfile.sys', disabled: true},
      ],
    },
  ];

  readonly selected = signal([]);
  readonly selectedCount = computed(() => this.selected().length);
}
```

### HTML

```
<div class="win95-file-explorer">
  <div class="title-bar">
    <div class="title-bar-text">Exploring - (C:)</div>
    <div class="title-bar-controls">
      <button tabindex="-1" aria-label="Minimize"><span>-</span></button>
      <button tabindex="-1" aria-label="Maximize"><span>&#x25A1;</span></button>
      <button tabindex="-1" aria-label="Close"><span>&#xd7;</span></button>
    </div>
  </div>

  <div class="menu-bar">
    <div class="menu-item"><u>F</u>ile</div>
    <div class="menu-item"><u>E</u>dit</div>
    <div class="menu-item"><u>V</u>iew</div>
    <div class="menu-item"><u>T</u>ools</div>
    <div class="menu-item"><u>H</u>elp</div>
  </div>

  <div class="toolbar">
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2190;</span> Back</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2192;</span> Forward</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2191;</span> Up</button>
  </div>

  <div class="tree-view">
    <ul ngTree #tree="ngTree" class="retro-tree" [(value)]="selected">
      <ng-template
        [ngTemplateOutlet]="treeNodes"
        [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
      />
    </ul>
  </div>

  <div class="status-bar">
    <div class="status-panel status-panel-grow">{{ selectedCount() }} object(s) selected</div>
    <div class="status-panel status-panel-right">4.5MB</div>
  </div>
</div>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [(expanded)]="node.expanded"
      [disabled]="node.disabled"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true">
        @if (node.children) {
          {{ treeItem.expanded() ? '📂' : '📁' }}
        } @else {
          📄
        }
      </span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Jersey+20&display=swap');

:host {
  display: flex;
  justify-content: center;
  user-select: none;

  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;
  --win95-blue: #000080;
  --win95-active-blue: linear-gradient(to right, #000080, #1084d0);
  --win95-font: "Jersey 20", sans-serif;

  font-family: var(--win95-font);
  font-size: 1.1rem;
}

.win95-file-explorer {
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  background-color: var(--win95-gray);
  min-width: 350px;
}

.win95-btn {
  background-color: var(--win95-gray);
  padding: 3px 8px;
  cursor: pointer;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.win95-btn:active {
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
  padding: 4px 7px 2px 9px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
}

.title-bar {
  background: var(--win95-active-blue);
  color: var(--win95-light);
  padding: 3px 6px;
  height: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.title-bar-text {
  font-weight: bold;
}

.title-bar-controls button {
  background: var(--win95-gray);
  border: 1px solid var(--win95-light);
  color: var(--win95-shadow);
  width: 16px;
  height: 14px;
  line-height: 8px;
  font-size: 14px;
  padding: 0;
  margin-left: 1px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.title-bar-controls button span {
  display: block;
  margin-top: -3px;
}

.menu-bar {
  display: flex;
  background-color: var(--win95-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  padding: 1px 2px;
}

.menu-item {
  padding: 0 6px;
  cursor: default;
  margin-right: 4px;
}

.menu-item:hover {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.toolbar {
  display: flex;
  align-items: center;
  padding: 4px;
  border-top: 1px solid var(--win95-light);
  border-bottom: 1px solid var(--win95-dark-gray);
}

.toolbar .win95-btn {
  display: flex;
  align-items: center;
  margin-right: 8px;
}

.toolbar .icon {
  font-size: 1.25rem;
  line-height: 1;
  margin-right: 4px;
}

.tree-view {
  background-color: var(--win95-light);
  min-height: 300px;
  padding: 4px;
  border-width: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  color: var(--win95-shadow);
}

.status-bar {
  margin-top: 2px;
}

.status-panel-grow {
  flex-grow: 1;
}

.status-panel-right {
  text-align: right;
}

.status-bar {
  height: 18px;
  background-color: var(--win95-gray);
  border-top: 1px solid var(--win95-light);
  border-left: 1px solid var(--win95-light);
  border-right: 1px solid var(--win95-dark-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  display: flex;
  font-size: 14px;
}

.status-panel {
  padding: 0 4px;
  height: 100%;
  display: flex;
  align-items: center;
  margin-right: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  border-width: 1px;
  flex-grow: 1;
}

.status-panel:last-child {
  flex-grow: 0;
  width: 120px;
}

[ngTree] {
  padding: 0;
  margin: 0;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.25rem 0.75rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--senary-contrast);
  color: var(--primary-contrast);
  outline: none;
}

[ngTreeItem][aria-selected='true'] {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

Leave `[multi]="false"` (the default) for single selection. Users press Space to select the focused item.

### [Multi-selection](#multi-selection)

Allow users to select multiple items from the tree.

### TS

```
import {Component, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: TreeNode[] = [
    {
      name: 'public',
      value: 'public',
      children: [
        {name: 'index.html', value: 'public/index.html'},
        {name: 'favicon.ico', value: 'public/favicon.ico'},
        {name: 'styles.css', value: 'public/styles.css'},
      ],
      expanded: true,
    },
    {
      name: 'src',
      value: 'src',
      children: [
        {
          name: 'app',
          value: 'src/app',
          children: [
            {name: 'app.ts', value: 'src/app/app.ts'},
            {name: 'app.html', value: 'src/app/app.html'},
            {name: 'app.css', value: 'src/app/app.css'},
          ],
          expanded: false,
        },
        {
          name: 'assets',
          value: 'src/assets',
          children: [{name: 'logo.png', value: 'src/assets/logo.png'}],
          expanded: false,
        },
        {
          name: 'environments',
          value: 'src/environments',
          children: [
            {
              name: 'environment.prod.ts',
              value: 'src/environments/environment.prod.ts',
              expanded: false,
            },
            {name: 'environment.ts', value: 'src/environments/environment.ts'},
          ],
          expanded: false,
        },
        {name: 'main.ts', value: 'src/main.ts'},
        {name: 'polyfills.ts', value: 'src/polyfills.ts'},
        {name: 'styles.css', value: 'src/styles.css', disabled: true},
        {name: 'test.ts', value: 'src/test.ts'},
      ],
      expanded: false,
    },
    {name: 'angular.json', value: 'angular.json'},
    {name: 'package.json', value: 'package.json'},
    {name: 'README.md', value: 'README.md'},
  ];

  readonly selected = signal(['angular.json', 'public/styles.css']);
}
```

### HTML

```
<ul ngTree #tree="ngTree" [multi]="true" [(value)]="selected" class="basic-tree">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [disabled]="node.disabled"
      [(expanded)]="node.expanded"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true" class="material-symbols-outlined expand-icon" translate="no">{{
        node.children ? 'chevron_right' : ''
      }}</span>
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        node.children ? 'folder' : 'docs'
      }}</span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  user-select: none;
  font-family: var(--inter-font);
}

[ngTree] {
  min-width: 24rem;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.3rem 1rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--quinary-contrast);
}

[ngTreeItem]:focus {
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngTreeItem][aria-selected='true'],
[ngTreeItem][aria-selected='true'] .expand-icon {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

.material-symbols-outlined {
  margin: 0;
  width: 24px;
}

.expand-icon {
  transition: transform 0.2s ease;
}

[ngTreeItem][aria-expanded='true'] .expand-icon {
  transform: rotate(90deg);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

### TS

```
import {Component, computed, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: readonly TreeNode[] = [
    {
      name: 'C:',
      value: 'C:',
      expanded: true,
      children: [
        {
          name: 'Program Files/',
          value: 'C:/Program Files',
          children: [
            {name: 'Common Files', value: 'C:/Program Files/Common Files'},
            {name: 'Internet Explorer', value: 'C:/Program Files/Internet Explorer'},
          ],
          expanded: false,
        },
        {
          name: 'Users/',
          value: 'C:/Users',
          children: [
            {name: 'Default', value: 'C:/Users/Default'},
            {name: 'Public', value: 'C:/Users/Public'},
          ],
          expanded: false,
        },
        {
          name: 'Windows/',
          value: 'C:/Windows',
          children: [
            {name: 'System32', value: 'C:/Windows/System32'},
            {name: 'Web', value: 'C:/Windows/Web'},
          ],
          expanded: false,
        },
        {name: 'pagefile.sys', value: 'C:/pagefile.sys'},
        {name: 'swapfile.sys', value: 'C:/swapfile.sys', disabled: true},
      ],
    },
  ];

  readonly selected = signal([]);
  readonly selectedCount = computed(() => this.selected().length);
}
```

### HTML

```
<div class="win95-file-explorer">
  <div class="title-bar">
    <div class="title-bar-text">Exploring - (C:)</div>
    <div class="title-bar-controls">
      <button tabindex="-1" aria-label="Minimize"><span>-</span></button>
      <button tabindex="-1" aria-label="Maximize"><span>&#x25A1;</span></button>
      <button tabindex="-1" aria-label="Close"><span>&#xd7;</span></button>
    </div>
  </div>

  <div class="menu-bar">
    <div class="menu-item"><u>F</u>ile</div>
    <div class="menu-item"><u>E</u>dit</div>
    <div class="menu-item"><u>V</u>iew</div>
    <div class="menu-item"><u>T</u>ools</div>
    <div class="menu-item"><u>H</u>elp</div>
  </div>

  <div class="toolbar">
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2190;</span> Back</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2192;</span> Forward</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2191;</span> Up</button>
  </div>

  <div class="tree-view">
    <ul ngTree #tree="ngTree" class="retro-tree" [(value)]="selected" [multi]="true">
      <ng-template
        [ngTemplateOutlet]="treeNodes"
        [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
      />
    </ul>
  </div>

  <div class="status-bar">
    <div class="status-panel status-panel-grow">{{ selectedCount() }} object(s) selected</div>
    <div class="status-panel status-panel-right">4.5MB</div>
  </div>
</div>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [(expanded)]="node.expanded"
      [disabled]="node.disabled"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true">
        @if (node.children) {
          {{ treeItem.expanded() ? '📂' : '📁' }}
        } @else {
          📄
        }
      </span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Jersey+20&display=swap');

:host {
  display: flex;
  justify-content: center;
  user-select: none;

  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;
  --win95-blue: #000080;
  --win95-active-blue: linear-gradient(to right, #000080, #1084d0);
  --win95-font: "Jersey 20", sans-serif;

  font-family: var(--win95-font);
  font-size: 1.1rem;
}

.win95-file-explorer {
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  background-color: var(--win95-gray);
  min-width: 350px;
}

.win95-btn {
  background-color: var(--win95-gray);
  padding: 3px 8px;
  cursor: pointer;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.win95-btn:active {
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
  padding: 4px 7px 2px 9px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
}

.title-bar {
  background: var(--win95-active-blue);
  color: var(--win95-light);
  padding: 3px 6px;
  height: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.title-bar-text {
  font-weight: bold;
}

.title-bar-controls button {
  background: var(--win95-gray);
  border: 1px solid var(--win95-light);
  color: var(--win95-shadow);
  width: 16px;
  height: 14px;
  line-height: 8px;
  font-size: 14px;
  padding: 0;
  margin-left: 1px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.title-bar-controls button span {
  display: block;
  margin-top: -3px;
}

.menu-bar {
  display: flex;
  background-color: var(--win95-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  padding: 1px 2px;
}

.menu-item {
  padding: 0 6px;
  cursor: default;
  margin-right: 4px;
}

.menu-item:hover {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.toolbar {
  display: flex;
  align-items: center;
  padding: 4px;
  border-top: 1px solid var(--win95-light);
  border-bottom: 1px solid var(--win95-dark-gray);
}

.toolbar .win95-btn {
  display: flex;
  align-items: center;
  margin-right: 8px;
}

.toolbar .icon {
  font-size: 1.25rem;
  line-height: 1;
  margin-right: 4px;
}

.tree-view {
  background-color: var(--win95-light);
  min-height: 300px;
  padding: 4px;
  border-width: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  color: var(--win95-shadow);
}

.status-bar {
  margin-top: 2px;
}

.status-panel-grow {
  flex-grow: 1;
}

.status-panel-right {
  text-align: right;
}

.status-bar {
  height: 18px;
  background-color: var(--win95-gray);
  border-top: 1px solid var(--win95-light);
  border-left: 1px solid var(--win95-light);
  border-right: 1px solid var(--win95-dark-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  display: flex;
  font-size: 14px;
}

.status-panel {
  padding: 0 4px;
  height: 100%;
  display: flex;
  align-items: center;
  margin-right: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  border-width: 1px;
  flex-grow: 1;
}

.status-panel:last-child {
  flex-grow: 0;
  width: 120px;
}

[ngTree] {
  padding: 0;
  margin: 0;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.25rem 0.75rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--senary-contrast);
  color: var(--primary-contrast);
  outline: none;
}

[ngTreeItem][aria-selected='true'] {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

Set `[multi]="true"` on the tree. Users select items individually with Space or select ranges with Shift+Arrow keys.

### [Selection follows focus](#selection-follows-focus)

When selection follows focus, the focused item is automatically selected. This simplifies interaction for navigation scenarios.

### TS

```
import {Component, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: TreeNode[] = [
    {
      name: 'public',
      value: 'public',
      children: [
        {name: 'index.html', value: 'public/index.html'},
        {name: 'favicon.ico', value: 'public/favicon.ico'},
        {name: 'styles.css', value: 'public/styles.css'},
      ],
      expanded: true,
    },
    {
      name: 'src',
      value: 'src',
      children: [
        {
          name: 'app',
          value: 'src/app',
          children: [
            {name: 'app.ts', value: 'src/app/app.ts'},
            {name: 'app.html', value: 'src/app/app.html'},
            {name: 'app.css', value: 'src/app/app.css'},
          ],
          expanded: false,
        },
        {
          name: 'assets',
          value: 'src/assets',
          children: [{name: 'logo.png', value: 'src/assets/logo.png'}],
          expanded: false,
        },
        {
          name: 'environments',
          value: 'src/environments',
          children: [
            {
              name: 'environment.prod.ts',
              value: 'src/environments/environment.prod.ts',
              expanded: false,
            },
            {name: 'environment.ts', value: 'src/environments/environment.ts'},
          ],
          expanded: false,
        },
        {name: 'main.ts', value: 'src/main.ts'},
        {name: 'polyfills.ts', value: 'src/polyfills.ts'},
        {name: 'styles.css', value: 'src/styles.css', disabled: true},
        {name: 'test.ts', value: 'src/test.ts'},
      ],
      expanded: false,
    },
    {name: 'angular.json', value: 'angular.json'},
    {name: 'package.json', value: 'package.json'},
    {name: 'README.md', value: 'README.md'},
  ];

  readonly selected = signal(['angular.json']);
}
```

### HTML

```
<ul ngTree #tree="ngTree" [(value)]="selected" selectionMode="follow" class="basic-tree">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [disabled]="node.disabled"
      [(expanded)]="node.expanded"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true" class="material-symbols-outlined expand-icon" translate="no">{{
        node.children ? 'chevron_right' : ''
      }}</span>
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        node.children ? 'folder' : 'docs'
      }}</span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  user-select: none;
  font-family: var(--inter-font);
}

[ngTree] {
  min-width: 24rem;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.3rem 1rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--quinary-contrast);
}

[ngTreeItem]:focus {
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngTreeItem][aria-selected='true'],
[ngTreeItem][aria-selected='true'] .expand-icon {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

.material-symbols-outlined {
  margin: 0;
  width: 24px;
}

.expand-icon {
  transition: transform 0.2s ease;
}

[ngTreeItem][aria-expanded='true'] .expand-icon {
  transform: rotate(90deg);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

### TS

```
import {Component, computed, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: readonly TreeNode[] = [
    {
      name: 'C:',
      value: 'C:',
      expanded: true,
      children: [
        {
          name: 'Program Files/',
          value: 'C:/Program Files',
          children: [
            {name: 'Common Files', value: 'C:/Program Files/Common Files'},
            {name: 'Internet Explorer', value: 'C:/Program Files/Internet Explorer'},
          ],
          expanded: false,
        },
        {
          name: 'Users/',
          value: 'C:/Users',
          children: [
            {name: 'Default', value: 'C:/Users/Default'},
            {name: 'Public', value: 'C:/Users/Public'},
          ],
          expanded: false,
        },
        {
          name: 'Windows/',
          value: 'C:/Windows',
          children: [
            {name: 'System32', value: 'C:/Windows/System32'},
            {name: 'Web', value: 'C:/Windows/Web'},
          ],
          expanded: false,
        },
        {name: 'pagefile.sys', value: 'C:/pagefile.sys'},
        {name: 'swapfile.sys', value: 'C:/swapfile.sys', disabled: true},
      ],
    },
  ];

  readonly selected = signal([]);
  readonly selectedCount = computed(() => this.selected().length);
}
```

### HTML

```
<div class="win95-file-explorer">
  <div class="title-bar">
    <div class="title-bar-text">Exploring - (C:)</div>
    <div class="title-bar-controls">
      <button tabindex="-1" aria-label="Minimize"><span>-</span></button>
      <button tabindex="-1" aria-label="Maximize"><span>&#x25A1;</span></button>
      <button tabindex="-1" aria-label="Close"><span>&#xd7;</span></button>
    </div>
  </div>

  <div class="menu-bar">
    <div class="menu-item"><u>F</u>ile</div>
    <div class="menu-item"><u>E</u>dit</div>
    <div class="menu-item"><u>V</u>iew</div>
    <div class="menu-item"><u>T</u>ools</div>
    <div class="menu-item"><u>H</u>elp</div>
  </div>

  <div class="toolbar">
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2190;</span> Back</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2192;</span> Forward</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2191;</span> Up</button>
  </div>

  <div class="tree-view">
    <ul ngTree #tree="ngTree" class="retro-tree" selectionMode="follow" [(value)]="selected">
      <ng-template
        [ngTemplateOutlet]="treeNodes"
        [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
      />
    </ul>
  </div>

  <div class="status-bar">
    <div class="status-panel status-panel-grow">{{ selectedCount() }} object(s) selected</div>
    <div class="status-panel status-panel-right">4.5MB</div>
  </div>
</div>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [(expanded)]="node.expanded"
      [disabled]="node.disabled"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true">
        @if (node.children) {
          {{ treeItem.expanded() ? '📂' : '📁' }}
        } @else {
          📄
        }
      </span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Jersey+20&display=swap');

:host {
  display: flex;
  justify-content: center;
  user-select: none;

  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;
  --win95-blue: #000080;
  --win95-active-blue: linear-gradient(to right, #000080, #1084d0);
  --win95-font: "Jersey 20", sans-serif;

  font-family: var(--win95-font);
  font-size: 1.1rem;
}

.win95-file-explorer {
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  background-color: var(--win95-gray);
  min-width: 350px;
}

.win95-btn {
  background-color: var(--win95-gray);
  padding: 3px 8px;
  cursor: pointer;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.win95-btn:active {
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
  padding: 4px 7px 2px 9px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
}

.title-bar {
  background: var(--win95-active-blue);
  color: var(--win95-light);
  padding: 3px 6px;
  height: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.title-bar-text {
  font-weight: bold;
}

.title-bar-controls button {
  background: var(--win95-gray);
  border: 1px solid var(--win95-light);
  color: var(--win95-shadow);
  width: 16px;
  height: 14px;
  line-height: 8px;
  font-size: 14px;
  padding: 0;
  margin-left: 1px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.title-bar-controls button span {
  display: block;
  margin-top: -3px;
}

.menu-bar {
  display: flex;
  background-color: var(--win95-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  padding: 1px 2px;
}

.menu-item {
  padding: 0 6px;
  cursor: default;
  margin-right: 4px;
}

.menu-item:hover {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.toolbar {
  display: flex;
  align-items: center;
  padding: 4px;
  border-top: 1px solid var(--win95-light);
  border-bottom: 1px solid var(--win95-dark-gray);
}

.toolbar .win95-btn {
  display: flex;
  align-items: center;
  margin-right: 8px;
}

.toolbar .icon {
  font-size: 1.25rem;
  line-height: 1;
  margin-right: 4px;
}

.tree-view {
  background-color: var(--win95-light);
  min-height: 300px;
  padding: 4px;
  border-width: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  color: var(--win95-shadow);
}

.status-bar {
  margin-top: 2px;
}

.status-panel-grow {
  flex-grow: 1;
}

.status-panel-right {
  text-align: right;
}

.status-bar {
  height: 18px;
  background-color: var(--win95-gray);
  border-top: 1px solid var(--win95-light);
  border-left: 1px solid var(--win95-light);
  border-right: 1px solid var(--win95-dark-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  display: flex;
  font-size: 14px;
}

.status-panel {
  padding: 0 4px;
  height: 100%;
  display: flex;
  align-items: center;
  margin-right: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  border-width: 1px;
  flex-grow: 1;
}

.status-panel:last-child {
  flex-grow: 0;
  width: 120px;
}

[ngTree] {
  padding: 0;
  margin: 0;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.25rem 0.75rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--senary-contrast);
  color: var(--primary-contrast);
  outline: none;
}

[ngTreeItem][aria-selected='true'] {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

Set `[selectionMode]="'follow'"` on the tree. Selection automatically updates as users navigate with arrow keys.

### [Disabled tree items](#disabled-tree-items)

Disable specific tree nodes to prevent interaction. Control whether disabled items can receive focus.

### TS

```
import {Component, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: TreeNode[] = [
    {
      name: 'public',
      value: 'public',
      children: [
        {name: 'index.html', value: 'public/index.html'},
        {name: 'favicon.ico', value: 'public/favicon.ico'},
        {name: 'styles.css', value: 'public/styles.css'},
      ],
      expanded: true,
      disabled: true,
    },
    {
      name: 'src',
      value: 'src',
      children: [
        {
          name: 'app',
          value: 'src/app',
          children: [
            {name: 'app.ts', value: 'src/app/app.ts'},
            {name: 'app.html', value: 'src/app/app.html'},
            {name: 'app.css', value: 'src/app/app.css'},
          ],
          expanded: false,
        },
        {
          name: 'assets',
          value: 'src/assets',
          children: [{name: 'logo.png', value: 'src/assets/logo.png'}],
          expanded: false,
        },
        {
          name: 'environments',
          value: 'src/environments',
          children: [
            {
              name: 'environment.prod.ts',
              value: 'src/environments/environment.prod.ts',
              expanded: false,
            },
            {name: 'environment.ts', value: 'src/environments/environment.ts'},
          ],
          expanded: false,
        },
        {name: 'main.ts', value: 'src/main.ts'},
        {name: 'polyfills.ts', value: 'src/polyfills.ts'},
        {name: 'styles.css', value: 'src/styles.css', disabled: true},
        {name: 'test.ts', value: 'src/test.ts'},
      ],
      expanded: false,
      disabled: true,
    },
    {name: 'angular.json', value: 'angular.json'},
    {name: 'package.json', value: 'package.json'},
    {name: 'README.md', value: 'README.md'},
  ];

  readonly selected = signal(['angular.json']);
}
```

### HTML

```
<ul ngTree #tree="ngTree" [(value)]="selected" class="basic-tree">
  <ng-template
    [ngTemplateOutlet]="treeNodes"
    [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
  />
</ul>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [disabled]="node.disabled"
      [(expanded)]="node.expanded"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true" class="material-symbols-outlined expand-icon" translate="no">{{
        node.children ? 'chevron_right' : ''
      }}</span>
      <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{
        node.children ? 'folder' : 'docs'
      }}</span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  user-select: none;
  font-family: var(--inter-font);
}

[ngTree] {
  min-width: 24rem;
  background-color: var(--septenary-contrast);
  border-radius: 0.5rem;
  padding: 0.5rem;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.3rem 1rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--quinary-contrast);
}

[ngTreeItem]:focus {
  outline: 1px solid color-mix(in srgb, var(--hot-pink) 60%, transparent);
}

[ngTreeItem][aria-selected='true'],
[ngTreeItem][aria-selected='true'] .expand-icon {
  background-image: var(--pink-to-purple-horizontal-gradient);
  background-clip: text;
  color: transparent;
}

.material-symbols-outlined {
  margin: 0;
  width: 24px;
}

.expand-icon {
  transition: transform 0.2s ease;
}

[ngTreeItem][aria-expanded='true'] .expand-icon {
  transform: rotate(90deg);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

### TS

```
import {Component, computed, signal} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree';

type TreeNode = {
  name: string;
  value: string;
  children?: TreeNode[];
  disabled?: boolean;
  expanded?: boolean;
};

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet],
})
export class App {
  readonly nodes: readonly TreeNode[] = [
    {
      name: 'C:',
      value: 'C:',
      expanded: true,
      children: [
        {
          name: 'Program Files/',
          value: 'C:/Program Files',
          children: [
            {name: 'Common Files', value: 'C:/Program Files/Common Files'},
            {name: 'Internet Explorer', value: 'C:/Program Files/Internet Explorer'},
          ],
          expanded: true,
          disabled: true,
        },
        {
          name: 'Users/',
          value: 'C:/Users',
          children: [
            {name: 'Default', value: 'C:/Users/Default'},
            {name: 'Public', value: 'C:/Users/Public'},
          ],
          expanded: false,
        },
        {
          name: 'Windows/',
          value: 'C:/Windows',
          children: [
            {name: 'System32', value: 'C:/Windows/System32'},
            {name: 'Web', value: 'C:/Windows/Web'},
          ],
          expanded: false,
        },
        {name: 'pagefile.sys', value: 'C:/pagefile.sys'},
        {name: 'swapfile.sys', value: 'C:/swapfile.sys', disabled: true},
      ],
    },
  ];

  readonly selected = signal([]);
  readonly selectedCount = computed(() => this.selected().length);
}
```

### HTML

```
<div class="win95-file-explorer">
  <div class="title-bar">
    <div class="title-bar-text">Exploring - (C:)</div>
    <div class="title-bar-controls">
      <button tabindex="-1" aria-label="Minimize"><span>-</span></button>
      <button tabindex="-1" aria-label="Maximize"><span>&#x25A1;</span></button>
      <button tabindex="-1" aria-label="Close"><span>&#xd7;</span></button>
    </div>
  </div>

  <div class="menu-bar">
    <div class="menu-item"><u>F</u>ile</div>
    <div class="menu-item"><u>E</u>dit</div>
    <div class="menu-item"><u>V</u>iew</div>
    <div class="menu-item"><u>T</u>ools</div>
    <div class="menu-item"><u>H</u>elp</div>
  </div>

  <div class="toolbar">
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2190;</span> Back</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2192;</span> Forward</button>
    <button tabindex="-1" class="win95-btn"><span class="icon">&#x2191;</span> Up</button>
  </div>

  <div class="tree-view">
    <ul ngTree #tree="ngTree" class="retro-tree" [(value)]="selected">
      <ng-template
        [ngTemplateOutlet]="treeNodes"
        [ngTemplateOutletContext]="{nodes: nodes, parent: tree}"
      />
    </ul>
  </div>

  <div class="status-bar">
    <div class="status-panel status-panel-grow">{{ selectedCount() }} object(s) selected</div>
    <div class="status-panel status-panel-right">4.5MB</div>
  </div>
</div>

<ng-template #treeNodes let-nodes="nodes" let-parent="parent">
  @for (node of nodes; track node.value) {
    <li
      ngTreeItem
      [parent]="parent"
      [value]="node.value"
      [label]="node.name"
      [(expanded)]="node.expanded"
      [disabled]="node.disabled"
      #treeItem="ngTreeItem"
    >
      <span aria-hidden="true">
        @if (node.children) {
          {{ treeItem.expanded() ? '📂' : '📁' }}
        } @else {
          📄
        }
      </span>
      {{ node.name }}
      <span aria-hidden="true" class="material-symbols-outlined selected-icon" translate="no"
        >check</span
      >
    </li>

    @if (node.children) {
      <ul role="group">
        <ng-template ngTreeItemGroup [ownedBy]="treeItem" #group="ngTreeItemGroup">
          <ng-template
            [ngTemplateOutlet]="treeNodes"
            [ngTemplateOutletContext]="{nodes: node.children, parent: group}"
          />
        </ng-template>
      </ul>
    }
  }
</ng-template>
```

### CSS

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Jersey+20&display=swap');

:host {
  display: flex;
  justify-content: center;
  user-select: none;

  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;
  --win95-blue: #000080;
  --win95-active-blue: linear-gradient(to right, #000080, #1084d0);
  --win95-font: "Jersey 20", sans-serif;

  font-family: var(--win95-font);
  font-size: 1.1rem;
}

.win95-file-explorer {
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  background-color: var(--win95-gray);
  min-width: 350px;
}

.win95-btn {
  background-color: var(--win95-gray);
  padding: 3px 8px;
  cursor: pointer;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.win95-btn:active {
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
  padding: 4px 7px 2px 9px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
}

.title-bar {
  background: var(--win95-active-blue);
  color: var(--win95-light);
  padding: 3px 6px;
  height: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.title-bar-text {
  font-weight: bold;
}

.title-bar-controls button {
  background: var(--win95-gray);
  border: 1px solid var(--win95-light);
  color: var(--win95-shadow);
  width: 16px;
  height: 14px;
  line-height: 8px;
  font-size: 14px;
  padding: 0;
  margin-left: 1px;
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
}

.title-bar-controls button span {
  display: block;
  margin-top: -3px;
}

.menu-bar {
  display: flex;
  background-color: var(--win95-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  padding: 1px 2px;
}

.menu-item {
  padding: 0 6px;
  cursor: default;
  margin-right: 4px;
}

.menu-item:hover {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.toolbar {
  display: flex;
  align-items: center;
  padding: 4px;
  border-top: 1px solid var(--win95-light);
  border-bottom: 1px solid var(--win95-dark-gray);
}

.toolbar .win95-btn {
  display: flex;
  align-items: center;
  margin-right: 8px;
}

.toolbar .icon {
  font-size: 1.25rem;
  line-height: 1;
  margin-right: 4px;
}

.tree-view {
  background-color: var(--win95-light);
  min-height: 300px;
  padding: 4px;
  border-width: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  color: var(--win95-shadow);
}

.status-bar {
  margin-top: 2px;
}

.status-panel-grow {
  flex-grow: 1;
}

.status-panel-right {
  text-align: right;
}

.status-bar {
  height: 18px;
  background-color: var(--win95-gray);
  border-top: 1px solid var(--win95-light);
  border-left: 1px solid var(--win95-light);
  border-right: 1px solid var(--win95-dark-gray);
  border-bottom: 1px solid var(--win95-dark-gray);
  display: flex;
  font-size: 14px;
}

.status-panel {
  padding: 0 4px;
  height: 100%;
  display: flex;
  align-items: center;
  margin-right: 1px;
  border-style: solid;
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  border-width: 1px;
  flex-grow: 1;
}

.status-panel:last-child {
  flex-grow: 0;
  width: 120px;
}

[ngTree] {
  padding: 0;
  margin: 0;
}

[ngTreeItem] {
  cursor: pointer;
  list-style: none;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.25rem 0.75rem;
}

[ngTreeItem][aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

[ngTreeItem]:focus,
[ngTreeItem]:hover {
  background-color: var(--senary-contrast);
  color: var(--primary-contrast);
  outline: none;
}

[ngTreeItem][aria-selected='true'] {
  background-color: var(--win95-blue);
  color: var(--win95-light);
}

.selected-icon {
  visibility: hidden;
  margin-left: auto;
}

[ngTreeItem][aria-current] .selected-icon,
[ngTreeItem][aria-selected='true'] .selected-icon {
  visibility: visible;
}

li[aria-expanded='false'] + ul[role='group'] {
  display: none;
}
```

When `[softDisabled]="true"` on the tree, disabled items can receive focus but cannot be activated or selected. When `[softDisabled]="false"`, disabled items are skipped during keyboard navigation.

## [Testing](#testing)

Angular Aria provides component harnesses for testing tree components. Here is an example of how to use the harnesses in a component test:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {TreeHarness} from '@angular/aria/tree/testing';
import {MyTreeComponent} from './my-tree'; // Your component

describe('MyTreeComponent', () => {
  let fixture: ComponentFixture<MyTreeComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyTreeComponent],
    });

    fixture = TestBed.createComponent(MyTreeComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should navigate and expand tree items', async () => {
    const tree = await loader.getHarness(TreeHarness);

    // Get top-level structure representation
    expect(await tree.getTreeStructure()).toEqual({
      children: [{text: 'public'}, {text: 'src'}, {text: 'package.json'}],
    });

    // Get all items (currently visible)
    const items = await tree.getItems();
    expect(items.length).toBe(3);

    // Expand the first item ('public')
    expect(await items[0].isExpanded()).toBe(false);
    await items[0].click();
    expect(await items[0].isExpanded()).toBe(true);

    // Verifying tree structure updates after expansion
    expect(await tree.getTreeStructure()).toEqual({
      children: [
        {
          text: 'public',
          children: [{text: 'index.html'}, {text: 'styles.css'}],
        },
        {text: 'src'},
        {text: 'package.json'},
      ],
    });
  });
});
```

## [API reference](#api-reference)

For detailed API documentation, inspect the following API references:

* [`Tree`](/api/aria/tree/Tree)
* [`TreeItem`](/api/aria/tree/TreeItem)
* [`TreeItemGroup`](/api/aria/tree/TreeItemGroup)

----
url: https://angular.dev/guide/routing/route-transition-animations
----

Route transition animations enhance user experience by providing smooth visual transitions when navigating between different views in your Angular application. [Angular Router](/guide/routing) includes built-in support for the browser's View Transitions API, enabling seamless animations between route changes in supported browsers.

**HELPFUL:** The Router's native View Transitions integration is currently in [developer preview](/reference/releases#developer-preview). Native View Transitions are a relatively new browser feature with limited support across all browsers.

## [How View Transitions work](#how-view-transitions-work)

View transitions use the browser's native [`document.startViewTransition` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) to create smooth animations between different states of your application. The API works by:

1. **Capturing the current state** - The browser takes a screenshot of the current page
2. **Executing the DOM update** - Your callback function runs to update the DOM
3. **Capturing the new state** - The browser captures the updated page state
4. **Playing the transition** - The browser animates between the old and new states

Here's the basic structure of the `startViewTransition` API:

```
document.startViewTransition(async () => {
  await updateTheDOMSomehow();
});
```

For more details about the browser API, see the [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions).

## [How the Router uses view transitions](#how-the-router-uses-view-transitions)

Angular Router integrates view transitions into the navigation lifecycle to create seamless route changes. During navigation, the Router:

1. **Completes navigation preparation** - Route matching, [lazy loading](guide/routing/loading-strategies#lazily-loaded-components-and-routes), [guards](/guide/routing/route-guards), [resources](/guide/routing/data-fetching-with-resources), and [resolvers](/guide/routing/data-resolvers) execute
2. **Initiates the view transition** - Router calls `startViewTransition` when routes are ready for activation
3. **Updates the DOM** - Router activates new routes and deactivates old ones within the transition callback
4. **Finalizes the transition** - The transition Promise resolves when Angular completes rendering

The Router's view transition integration acts as a [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). When browsers don't support the View Transitions API, the Router performs normal DOM updates without animation, ensuring your application works across all browsers.

## [Enabling View Transitions in the Router](#enabling-view-transitions-in-the-router)

Enable view transitions by adding the [`withViewTransitions`](/api/router/withViewTransitions) feature to your [router configuration](/guide/routing/define-routes#adding-the-router-to-your-application). Angular supports both standalone and NgModule bootstrap approaches:

### [Standalone bootstrap](#standalone-bootstrap)

```
import {bootstrapApplication} from '@angular/platform-browser';
import {provideRouter, withViewTransitions} from '@angular/router';
import {routes} from './app.routes';

bootstrapApplication(MyApp, {
  providers: [provideRouter(routes, withViewTransitions())],
});
```

### [NgModule bootstrap](#ngmodule-bootstrap)

```
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';

@NgModule({
  imports: [RouterModule.forRoot(routes, {enableViewTransitions: true})],
})
export class AppRouting {}
```

[Try the "count" example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-2dnvtm?file=src%2Fmain.ts)

This example demonstrates how router navigation can replace direct `startViewTransition` calls for counter updates.

## [Customizing transitions with CSS](#customizing-transitions-with-css)

You can customize view transitions using CSS to create unique animation effects. The browser creates separate transition elements that you can target with CSS selectors.

To create custom transitions:

1. **Add view-transition-name** - Assign unique names to elements you want to animate
2. **Define global animations** - Create CSS animations in your global styles
3. **Target transition pseudo-elements** - Use `::view-transition-old()` and `::view-transition-new()` selectors

Here's an example that adds a rotation effect to a counter element:

```
/* Define keyframe animations */
@keyframes rotate-out {
  to {
    transform: rotate(90deg);
  }
}

@keyframes rotate-in {
  from {
    transform: rotate(-90deg);
  }
}

/* Target view transition pseudo-elements */
::view-transition-old(count),
::view-transition-new(count) {
  animation-duration: 200ms;
  animation-name: -ua-view-transition-fade-in, rotate-in;
}

::view-transition-old(count) {
  animation-name: -ua-view-transition-fade-out, rotate-out;
}
```

**IMPORTANT:** Define view transition animations in your global styles file, not in component styles. Angular's [view encapsulation](/guide/components/styling#style-scoping) scopes component styles, which prevents them from targeting the transition pseudo-elements correctly.

[Try the updated “count” example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-fwn4i7?file=src%2Fmain.ts)

## [Advanced transition control with onViewTransitionCreated](#advanced-transition-control-with-onviewtransitioncreated)

The [`withViewTransitions`](/api/router/withViewTransitions) feature accepts an options object with an `onViewTransitionCreated` callback for advanced control over view transitions. This callback:

* Runs in an [injection context](/guide/di/dependency-injection-context#run-within-an-injection-context)

* Receives a [`ViewTransitionInfo`](/api/router/ViewTransitionInfo) object containing:

  * The `ViewTransition` instance from `startViewTransition`
  * The [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) for the route being navigated from
  * The [`ActivatedRouteSnapshot`](/api/router/ActivatedRouteSnapshot) for the route being navigated to

Use this callback to customize transition behavior based on navigation context. For example, you can skip transitions for specific navigation types:

```
import {inject} from '@angular/core';
import {Router, withViewTransitions, isActive, IsActiveMatchOptions} from '@angular/router';

withViewTransitions({
  onViewTransitionCreated: ({transition}) => {
    const router = inject(Router);
    const targetUrl = router.currentNavigation()!.finalUrl!;

    // Skip transition if only fragment or query params change
    const config: Partial<IsActiveMatchOptions> = {
      paths: 'exact',
      matrixParams: 'exact',
      fragment: 'ignored',
      queryParams: 'ignored',
    };

    const isTargetRouteCurrent = isActive(targetUrl, router, config);

    if (isTargetRouteCurrent()) {
      transition.skipTransition();
    }
  },
});
```

This example skips the view transition when navigation only changes the [URL fragment or query parameters](/guide/routing/read-route-state#query-parameters) (such as anchor links within the same page). The `skipTransition()` method prevents the animation while still allowing the navigation to complete.

## [Examples from the Chrome explainer adapted to Angular](#examples-from-the-chrome-explainer-adapted-to-angular)

The following examples demonstrate various view transition techniques adapted from the Chrome team's documentation for use with Angular Router:

### [Transitioning elements don't need to be the same DOM element](#transitioning-elements-dont-need-to-be-the-same-dom-element)

Elements can transition smoothly between different DOM elements as long as they share the same `view-transition-name`.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning_elements_dont_need_to_be_the_same_dom_element)
* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-dh8npr?file=src%2Fmain.ts)

### [Custom entry and exit animations](#custom-entry-and-exit-animations)

Create unique animations for elements entering and leaving the viewport during route transitions.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#custom_entry_and_exit_transitions)
* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-8kly3o)

### [Async DOM updates and waiting for content](#async-dom-updates-and-waiting-for-content)

Angular Router prioritizes immediate transitions over waiting for additional content to load.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#async_dom_updates_and_waiting_for_content)

**NOTE:** Angular Router does not provide a way to delay view transitions. This design choice prevents pages from becoming non-interactive while waiting for additional content. As the Chrome documentation notes: "During this time, the page is frozen, so delays here should be kept to a minimum…in some cases it's better to avoid the delay altogether, and use the content you already have."

### [Handle multiple view transition styles with view transition types](#handle-multiple-view-transition-styles-with-view-transition-types)

Use view transition types to apply different animation styles based on navigation context.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types)
* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-vxzcam)

### [Handle multiple view transition styles with a class name on the view transition root (deprecated)](#handle-multiple-view-transition-styles-with-a-class-name-on-the-view-transition-root-deprecated)

This approach uses CSS classes on the transition root element to control animation styles.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#changing-on-navigation-type)
* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-nmnzzg?file=src%2Fmain.ts)

### [Transitioning without freezing other animations](#transitioning-without-freezing-other-animations)

Maintain other page animations during view transitions to create more dynamic user experiences.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning-without-freezing)
* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-76kgww)

### [Animating with JavaScript](#animating-with-javascript)

Control view transitions programmatically using JavaScript APIs for complex animation scenarios.

* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#animating-with-javascript)
* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-cklnkm)

----
url: https://angular.dev/tutorials/learn-angular/9-output
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/8-input)[](/tutorials/learn-angular/10-deferrable-views)

[](/tutorials/learn-angular/8-input)[](/tutorials/learn-angular/10-deferrable-views)

When working with components it may be required to notify other components that something has happened. Perhaps a button has been clicked, an item has been added/removed from a list or some other important update has occurred. In this scenario components need to communicate with parent components.

Angular uses the [`output()`](/api/core/output) function to enable this type of behavior.

**NOTE:** Learn more about [custom events in the outputs guide](/guide/components/outputs).

In this activity, you'll learn how to use the [`output()`](/api/core/output) function to communicate with components.

***

To create the communication path from child to parent components, use the [`output`](/api/core/output) function to initialize a class property.

### child.ts

```
@Component({...})
class Child {
  incrementCountEvent = output<number>();
}
```

Now the component can generate events that can be listened to by the parent component. Trigger events by calling the `emit` method:

### child.ts

```
class Child {
  ...

  onClick() {
    this.count++;
    this.incrementCountEvent.emit(this.count);
  }
}
```

The emit function will generate an event with the same type as defined by the [`output`](/api/core/output).

Alright, your turn to give this a try. Complete the code by following these tasks:

1. ### [Add an `output()` property](#add-an-output-property)

   Update `child.ts` by adding an output property called `addItemEvent`, be sure to set the output type to be `string`.

2. ### [Complete `addItem` method](#complete-additem-method)

   In `child.ts` update the `addItem` method; use the following code as the logic:

   ### child.ts

   ```
   addItem() {
     this.addItemEvent.emit('🐢');
   }
   ```

3. ### [Update the `App` template](#update-the-app-template)

   In `app.ts` update the template to listen to the emitted event by adding the following code:

   ```
   <app-child (addItemEvent)="addItem($event)" />
   ```

   Now, the "Add Item" button adds a new item to the list every time the button is clicked.

Wow, at this point you've completed the component fundamentals - impressive 👏

Keep learning to unlock more of Angular's great features.

----
url: https://angular.dev/api/common/APP_BASE_HREF
----

```
    
      const APP_BASE_HREF: InjectionToken<string>;
    
    
```

## [Usage Notes](#usage-notes)

The following example shows how to use this token to configure the root app injector with a base href value, so that the DI framework can supply the dependency anywhere in the app.

```
import {NgModule} from '@angular/core';
import {APP_BASE_HREF} from '@angular/common';

@NgModule({
  providers: [{provide: APP_BASE_HREF, useValue: '/my/app'}]
})
class AppModule {}
```

Jump to details

----
url: https://angular.dev/api/core/provideNgReflectAttributes
----

```
function provideNgReflectAttributes(): EnvironmentProviders;
```

@returns`EnvironmentProviders`

## [Description](#description)

Enables the logic to produce `ng-reflect-*` attributes on elements with bindings.

Note: this is a dev-mode only setting and it will have no effect in production mode. In production mode, the `ng-reflect-*` attributes are *never* produced by Angular.

Important: using and relying on the `ng-reflect-*` attributes is not recommended, they are deprecated and only present for backwards compatibility. Angular will stop producing them in one of the future versions.

Jump to details

----
url: https://angular.dev/api/forms/signals/MaxLengthValidationError
----

```
    
      class MaxLengthValidationError extends BaseNgValidationError {}
    
    
```

@parammaxLength`number`

@paramoptions`ValidationErrorOptions | undefined`

@returns`MaxLengthValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/api/forms/signals/MAX_LENGTH
----

```
    
      const MAX_LENGTH: LimitKey<number>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/WithOptionalFieldTree
----

```
    
      type WithOptionalFieldTree<T> = Omit<T, 'fieldTree'> & {  fieldTree?: ReadonlyFieldTree<unknown>;}
    
    
```

Jump to details

----
url: https://angular.dev/tutorials/signals/3-deriving-state-with-linked-signals
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/signals/2-deriving-state-with-computed-signals)[](/tutorials/signals/4-managing-async-data-with-signals)

[](/tutorials/signals/2-deriving-state-with-computed-signals)[](/tutorials/signals/4-managing-async-data-with-signals)

Now that you've learned [how to derive state with computed signals](/tutorials/signals/2-deriving-state-with-computed-signals), you created a computed signal for `notificationsEnabled` that automatically followed your user status. But what if users want to manually disable notifications even when they're online? That's where linked signals come in.

Linked signals are writable signals that maintain a reactive connection to their source signals. They're perfect for creating state that normally follows a computation but can be overridden when needed.

In this activity, you'll learn how [`linkedSignal()`](/api/core/linkedSignal) differs from [`computed()`](/api/core/computed) by enhancing the previous user status system's computed `notificationsEnabled` to a writable linked signal.

***

1. ### [Import linkedSignal function](#import-linkedsignal-function)

   Add [`linkedSignal`](/api/core/linkedSignal) to your existing imports.

   ```
   // Add linkedSignal to existing imports
   import {Component, signal, computed, linkedSignal, ChangeDetectionStrategy} from '@angular/core';
   ```

2. ### [Convert computed to linkedSignal with the same expression](#convert-computed-to-linkedsignal-with-the-same-expression)

   Replace the computed `notificationsEnabled` with a linkedSignal using the exact same expression:

   ```
   // Previously (from lesson 2):
   // notificationsEnabled = computed(() => this.userStatus() === 'online');

   // Now with linkedSignal - same expression, but writable:
   notificationsEnabled = linkedSignal(() => this.userStatus() === 'online');
   ```

   The expression is identical, but linkedSignal creates a writable signal. It will still automatically update when `userStatus` changes, but you can also set it manually.

3. ### [Add a method to manually toggle notifications](#add-a-method-to-manually-toggle-notifications)

   Add a method to demonstrate that linked signals can be written to directly:

   ```
   toggleNotifications() {
     // This works with linkedSignal but would error with computed!
     this.notificationsEnabled.set(!this.notificationsEnabled());
   }
   ```

   This is the key difference: computed signals are read-only, but linked signals can be updated manually while still maintaining their reactive connection.

4. ### [Update the template to add manual notification control](#update-the-template-to-add-manual-notification-control)

   Update your template to add a toggle button for notifications:

   ```
   <div class="status-info">
     <div class="notifications">
       <strong>Notifications:</strong>
       @if (notificationsEnabled()) {
         Enabled
       } @else {
         Disabled
       }
       <button (click)="toggleNotifications()" class="override-btn">
         @if (notificationsEnabled()) {
           Disable
         } @else {
           Enable
         }
       </button>
     </div>
     <!-- existing message and working-hours divs remain -->
   </div>
   ```

5. ### [Observe the reactive behavior](#observe-the-reactive-behavior)

   Now test the behavior:

   1. Change the user status - notice how `notificationsEnabled` updates automatically
   2. Manually toggle notifications - it overrides the computed value
   3. Change status again - the linked signal re-syncs with its computation

   This demonstrates that linked signals maintain their reactive connection even after being manually set!

Excellent! You've learned the key differences between computed and linked signals:

* **Computed signals**: Read-only, always derived from other signals
* **Linked signals**: Writable, can be both derived AND manually updated
* **Use computed when**: The value should always be calculated
* **Use linkedSignal when**: You need a default computation that can be overridden

In the next lesson, you'll learn [how to manage async data with signals](/tutorials/signals/4-managing-async-data-with-signals)!

----
url: https://angular.dev/api/core/Host
----

```
@Host();
```

## [Usage Notes](#usage-notes)

The following shows use with the [`@Optional`](/api/core/Optional) decorator, and allows for a `null` result.

```
class OtherService {}
        class HostService {}

        @Directive({
          selector: 'child-directive',
          standalone: false,
        })
        class ChildDirective {
          logs: string[] = [];

          constructor(@Optional() @Host() os: OtherService, @Optional() @Host() hs: HostService) {
            // os is null: true
            this.logs.push(`os is null: ${os === null}`);
            // hs is an instance of HostService: true
            this.logs.push(`hs is an instance of HostService: ${hs instanceof HostService}`);
          }
        }

        @Component({
          selector: 'parent-cmp',
          viewProviders: [HostService],
          template: '<child-directive></child-directive>',
          standalone: false,
        })
        class ParentCmp {}

        @Component({
          selector: 'app',
          viewProviders: [OtherService],
          template: '<parent-cmp></parent-cmp>',
          standalone: false,
        })
        class App {}
```

For an extended example, see ["Dependency Injection Guide"](guide/di/hierarchical-dependency-injection#optional).

Jump to details

----
url: https://angular.dev/api/core/globals/getContext
----

```
function getContext<T extends {}>(element: Element): T | null;
```

@paramelement`Element`

Element for which to get the surrounding component instance.

@returns`T | null`

Instance of the component that is around the element or null if the element isn't inside any component.

Jump to details

----
url: https://angular.dev/api/forms/signals/OneOrMany
----

```
    
      type OneOrMany<T> = T | readonly T[]
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/signals/Validator
----

```
    
      type Validator<TValue, TPathKind extends PathKind = PathKind.Root> = LogicFn<  TValue,  ValidationResult,  TPathKind>
    
    
```

Jump to details

----
url: https://angular.dev/api/router/UrlMatcher
----

```
    
      type UrlMatcher = (  segments: UrlSegment[],  group: UrlSegmentGroup,  route: Route,) => UrlMatchResult | null
    
    
```

## [Description](#description)

A function for matching a route against URLs. Implement a custom URL matcher for [`Route.matcher`](/api/router/Route#matcher) when a combination of `path` and `pathMatch` is not expressive enough. Cannot be used together with `path` and `pathMatch`.

The function takes the following arguments and returns a [`UrlMatchResult`](/api/router/UrlMatchResult) object.

* *segments* : An array of URL segments.
* *group* : A segment group.
* *route* : The route to match against.

The following example implementation matches HTML files.

```
export function htmlFiles(url: UrlSegment[]) {
  return url.length === 1 && url[0].path.endsWith('.html') ? ({consumed: url}) : null;
}

export const routes = [{ matcher: htmlFiles, component: AnyComponent }];
```

Jump to details

----
url: https://angular.dev/extended-diagnostics/NG8107
----

This diagnostic detects when the left side of an optional chain operation (`.?`) does not include `null` or `undefined` in its type in Angular templates.

```
import {Component} from '@angular/core';

@Component({
  // Print the user's name safely, even if `user` is `null` or `undefined`.
  template: `<div>User name: {{ user?.name }}</div>`,
})
class MyComponent {
  // `user` is declared as an object which _cannot_ be `null` or `undefined`.
  user: {name: string} = {name: 'Angelino'};
}
```

## [What's wrong with that?](#whats-wrong-with-that)

Using the optional chain operator with a non-nullable input has no effect and is indicative of a discrepancy between the allowed type of a value and how it is presented in the template. A developer might reasonably assume that the output of the optional chain operator is could be `null` or `undefined`, but it will never actually be either of those values. This can lead to confusion about the expected output of the program.

## [What should I do instead?](#what-should-i-do-instead)

Update the template and declared type to be in sync. Double-check the type of the input and confirm whether it is actually expected to be nullable.

If the input should be nullable, add `null` or `undefined` to its type to indicate this.

```
import {Component} from '@angular/core';

@Component({
  // If `user` is nullish, `name` won't be evaluated and the expression will
  // return the nullish value (`null` or `undefined`).
  template: `<div>{{ user?.name }}</div>`,
})
class MyComponent {
  user: {name: string} | null = {name: 'Angelino'};
}
```

If the input should not be nullable, delete the `?` operator.

```
import {Component} from '@angular/core';

@Component({
  // Template always displays `name` as `user` is guaranteed to never be `null`
  // or `undefined`.
  template: `<div>{{ user.name }}</div>`,
})
class MyComponent {
  user: {name: string} = {name: 'Angelino'};
}
```

## [Configuration requirements](#configuration-requirements)

[`strictTemplates`](tools/cli/template-typecheck#strict-mode) must be enabled for any extended diagnostic to emit. [`strictNullChecks`](tools/cli/template-typecheck#strict-null-checks) must also be enabled to emit `optionalChainNotNullable` diagnostics.

## [What if I can't avoid this?](#what-if-i-cant-avoid-this)

This diagnostic can be disabled by editing the project's `tsconfig.json` file:

```
{
  "angularCompilerOptions": {
    "extendedDiagnostics": {
      "checks": {
        "optionalChainNotNullable": "suppress"
      }
    }
  }
}
```

See [extended diagnostic configuration](extended-diagnostics#configuration) for more info.

----
url: https://angular.dev/api/common/upgrade/UrlCodec
----

```
    
      abstract class UrlCodec {
}
    
    
```

Encodes the path from the provided string

@parampath`string`

The path string

@returns`string`

Decodes the path from the provided string

@parampath`string`

The path string

@returns`string`

Encodes the search string from the provided string or object

@paramsearch`string | { [k: string]: unknown; }`

The search string or object

@returns`string`

Decodes the search objects from the provided string

@paramsearch`string`

The search string

@returns`{ [k: string]: unknown; }`

Encodes the hash from the provided string

@paramhash`string`

The hash string

@returns`string`

Decodes the hash from the provided string

@paramhash`string`

The hash string

@returns`string`

Normalizes the URL from the provided string

@paramhref`string`

The URL string

@returns`string`

Normalizes the URL from the provided string, search, hash, and base URL parameters

@parampath`string`

The URL path

@paramsearch`{ [k: string]: unknown; }`

The search object

@paramhash`string`

The hash string

@parambaseUrl`string | undefined`

The base URL for the URL

@returns`string`

Checks whether the two strings are equal

@paramvalA`string`

First string for comparison

@paramvalB`string`

Second string for comparison

@returns`boolean`

Parses the URL string based on the base URL

@paramurl`string`

The full URL string

@parambase`string | undefined`

The base for the URL

@returns`{ href: string; protocol: string; host: string; search: string; hash: string; hostname: string; port: string; pathname: string; }`

Jump to details

----
url: https://angular.dev/api/aria/menu/MENU_COMPONENT
----

```
    
      const MENU_COMPONENT: InjectionToken<Menu<any> | MenuBar<any>>;
    
    
```

Jump to details

----
url: https://angular.dev/api/core/globals/applyChanges
----

```
function applyChanges(component: {}): void;
```

@paramcomponent`{}`

Component to [`mark for check`](/api/core/ChangeDetectorRef#markForCheck)

@returns`void`

Jump to details

----
url: https://angular.dev/api/core/testing/flushMicrotasks
----

```
function flushMicrotasks(): void;
```

@returns`void`

## [Description](#description)

**IMPORTANT:** This API requires Zone.js and cannot be used with the Vitest test runner

Flush any pending microtasks.

Jump to details

----
url: https://angular.dev/api/forms/signals/MetadataReducer
----

```
    
      interface MetadataReducer<TAcc, TItem> {}
    
    
```

The reduce function.

Gets the initial accumulated value.

Creates a reducer that accumulates a list of its individual item values.

@returns`MetadataReducer<TItem[], TItem | undefined>`

Creates a reducer that accumulates the min of its individual item values.

@returns`MetadataReducer<T | undefined, T | undefined>`

Creates a reducer that accumulates a the max of its individual item values.

@returns`MetadataReducer<T | undefined, T | undefined>`

Creates a reducer that logically or's its accumulated value with each individual item value.

@returns`MetadataReducer<boolean, boolean>`

Creates a reducer that logically and's its accumulated value with each individual item value.

@returns`MetadataReducer<boolean, boolean>`

Jump to details

----
url: https://angular.dev/api/forms/signals/StandardSchemaValidationError
----

```
    
      class StandardSchemaValidationError extends BaseNgValidationError {}
    
    
```

@paramissue`StandardSchemaV1.Issue`

@paramoptions`ValidationErrorOptions | undefined`

@returns`StandardSchemaValidationError`

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/guide/directives/attribute-directives
----

Attribute directives change the appearance or behavior of DOM elements and Angular components.

## [Use template bindings for one-off behavior](#use-template-bindings-for-one-off-behavior)

Angular's template syntax already covers changing a single element's classes, styles, properties, and events:

* [Class and style bindings](guide/templates/binding#css-class-and-style-property-bindings) add and remove CSS classes and inline styles.
* [Property and attribute bindings](guide/templates/binding) set DOM properties and HTML attributes.
* [Event listeners](guide/templates/event-listeners) respond to user interaction.

Attribute directives are useful when you want to package this kind of behavior into a reusable unit that you can apply to any element or component.

## [Building an attribute directive](#building-an-attribute-directive)

A custom attribute directive is a JavaScript class with the [`@Directive()`](/api/core/Directive) decorator. The decorator's `selector` defines the attribute that applies the directive. The square brackets make this an attribute selector, so the directive matches elements that carry the attribute. By convention, use a prefix such as `app` to avoid naming collisions:

```
import {Directive} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
})
export class HighlightDirective {}
```

**HELPFUL:** The CLI command [`ng generate directive`](tools/cli/schematics) scaffolds a directive along with its test file.

A directive can change its host declaratively through host bindings or imperatively through a reference to the host element. This example [injects](guide/di) [`ElementRef`](api/core/ElementRef) and accesses the element through its `nativeElement` property to set the background to yellow:

### highlight.directive.ts

```
import {Directive, ElementRef, inject} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
})
export class HighlightDirective {
  private el = inject(ElementRef);

  constructor() {
    this.el.nativeElement.style.backgroundColor = 'yellow';
  }
}
```

**IMPORTANT:** Directives *do not* support namespaces.

Avoid

```
<p app:Highlight>This is invalid</p>
```

## [Applying an attribute directive](#applying-an-attribute-directive)

To apply the directive, add its selector as an attribute on an element:

### app.component.html

```
<p appHighlight>Highlight me!</p>
```

Angular creates an instance of `HighlightDirective` for that `<p>` element, injects a reference to the element, and sets its background to yellow.

## [Handling user events](#handling-user-events)

To respond to user interaction, bind host element events to handler methods through the `host` property of the [`@Directive()`](/api/core/Directive) decorator. The following directive highlights the host element while the pointer is over it and clears the highlight when the pointer leaves:

### highlight.directive.ts

```
import {Directive, ElementRef, inject} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()',
  },
})
export class HighlightDirective {
  private el = inject(ElementRef);

  onMouseEnter() {
    this.highlight('yellow');
  }

  onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
```

The `host` property maps the `mouseenter` and `mouseleave` events to the `onMouseEnter()` and `onMouseLeave()` methods, which delegate to a `highlight()` helper that sets the background color on the host element. For more on host event bindings, see [binding to the host element](guide/components/host-elements#binding-to-the-host-element).

## [Accepting input values](#accepting-input-values)

Like components, directives accept inputs through the [`input()`](guide/components/inputs) function. Give an input the same name as the selector so that a single binding both applies the directive and passes a value to it:

### highlight.directive.ts

```
appHighlight = input('');
```

Read the input by calling it as a signal, and fall back to a default when no color is set:

### highlight.directive.ts

```
onMouseEnter() {
  this.highlight(this.appHighlight() || 'red');
}
```

In the template, bind the value to the selector. Because the input shares the selector's name, `[appHighlight]` both applies the directive and sets its value. Here the bound `color` is a property on the component:

### app.component.html

```
<p [appHighlight]="color">Highlight me!</p>
```

### app.component.ts

```
export class AppComponent {
  color = '';
}
```

A directive can declare more than one input. The following directive adds a `defaultColor` input, then falls back through `appHighlight`, `defaultColor`, and finally `red`:

### highlight.directive.ts

```
import {Directive, ElementRef, inject, input} from '@angular/core';

@Directive({
  selector: '[appHighlight]',
  host: {
    '(mouseenter)': 'onMouseEnter()',
    '(mouseleave)': 'onMouseLeave()',
  },
})
export class HighlightDirective {
  private el = inject(ElementRef);

  defaultColor = input('');

  appHighlight = input('');

  onMouseEnter() {
    this.highlight(this.appHighlight() || this.defaultColor() || 'red');
  }

  onMouseLeave() {
    this.highlight('');
  }

  private highlight(color: string) {
    this.el.nativeElement.style.backgroundColor = color;
  }
}
```

Bind both inputs on the same element. Because `defaultColor` takes a static string rather than a dynamic expression, it doesn't need square brackets:

### app.component.html

```
<p [appHighlight]="color" defaultColor="violet">Highlight me too!</p>
```

## [Deactivating Angular processing with `NgNonBindable`](#deactivating-angular-processing-with-ngnonbindable)

To prevent expression evaluation in the browser, add `ngNonBindable` to the host element. `ngNonBindable` deactivates interpolation, directives, and binding in templates.

In the following example, the expression `{{ 1 + 1 }}` renders just as it does in your code editor, and does not display `2`.

### app.component.html

```
<p>Use ngNonBindable to stop evaluation.</p>
<p ngNonBindable>This should not evaluate: {{ 1 + 1 }}</p>
```

Applying `ngNonBindable` to an element stops binding for that element's child elements. However, `ngNonBindable` still lets directives work on the element where you apply `ngNonBindable`. In the following example, the `appHighlight` directive is still active but Angular does not evaluate the expression `{{ 1 + 1 }}`.

### app.component.html

```
<h3>ngNonBindable with a directive</h3>
<div ngNonBindable [appHighlight]="'yellow'">
  This should not evaluate: {{ 1 + 1 }}, but will highlight yellow.
</div>
```

If you apply `ngNonBindable` to a parent element, Angular disables interpolation and binding of any sort, such as property binding or event binding, for the element's children.

## [What's next](#whats-next)

----
url: https://angular.dev/guide/forms/signals/form-submission
----

When a user submits a form, your application typically needs to handle multiple concerns at once: surfacing validation errors, preventing duplicate submission, sending data to a server, and much more. Handling each of these manually can be tedious and prone to error.

Signal Forms provides a [`submit()`](/api/forms/signals/submit) function that helps you manage the form submission lifecycle. This guide walks through how to use it.

## [What does `submit()` do?](#what-does-submit-do)

The [`submit()`](/api/forms/signals/submit) function runs through a specific sequence:

1. **Mark interactive fields as touched** — Fields that display errors only after being touched will now show their validation errors. Hidden, disabled, and readonly fields are skipped.
2. **Check validation** — If any validation rules have failed, submission stops and the `action` function does not run.
3. **Run the action** — The `action` function executes with the form's current value. While it runs, `submitting()` returns `true`.
4. **Handle the result** — If the action returns errors, they are routed to their target fields. If it returns nothing, the submission is treated as successful.

The [`submit()`](/api/forms/signals/submit) function returns a `Promise<boolean>` that resolves to `true` when the action completes without errors, and `false` when validation fails, the action returns errors, or a submission is already in progress. If the action throws, the promise rejects with that error.

## [Setting up form submission with `FormRoot`](#setting-up-form-submission-with-formroot)

The most common way to use the [`submit()`](/api/forms/signals/submit) function is through the [`FormRoot`](/api/forms/signals/FormRoot) directive.

The [`FormRoot`](/api/forms/signals/FormRoot) directive handles three things automatically when bound to a `<form>` element:

1. **Sets [`novalidate`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#novalidate)** — Disables the browser's built-in validation so Signal Forms manages validation instead
2. **Prevents default** — Stops the browser from navigating on form submission
3. **Calls [`submit()`](/api/forms/signals/submit)** — Triggers the submission flow when the user submits the form, if the form was created with a `submission` option

**NOTE:** The [`FormRoot`](/api/forms/signals/FormRoot) directive sets the `novalidate` attribute on the `form` element automatically. You do not need to add it manually when using [`FormRoot`](/api/forms/signals/FormRoot).

[`FormRoot`](/api/forms/signals/FormRoot) handles the submission event, but you still need to tell it *what to do* with the form data. That requires three things:

1. Bind your form to the [`FormRoot`](/api/forms/signals/FormRoot) directive
2. Pass a `submission` option to the [`form()`](/api/forms/signals/form) function
3. Define an `action` function within the `submission` option that manages the submitted data

```
import {Component, signal} from '@angular/core';
import {form, FormField, FormRoot, required} from '@angular/forms/signals';

@Component({
  selector: 'app-contact',
  imports: [FormField, FormRoot],
  template: `
    <form [formRoot]="contactForm">
      <label>
        Name
        <input [formField]="contactForm.name" />
      </label>

      <label>
        Email
        <input type="email" [formField]="contactForm.email" />
      </label>

      <button type="submit">Send</button>
    </form>
  `,
})
export class Contact {
  contactModel = signal({
    name: '',
    email: '',
  });

  contactForm = form(
    this.contactModel,
    (schemaPath) => {
      required(schemaPath.name);
      required(schemaPath.email);
    },
    {
      submission: {
        action: async (field) => {
          const result = await saveContact(field().value());
          if (result.ok) return;

          return {kind: 'serverError', message: 'Failed to submit form'};
        },
      },
    },
  );
}
```

The `action` function runs only when no validation rules have failed. By default, pending async validators do not block submission (see [Controlling validation gating](#controlling-validation-gating-with-ignorevalidators) for more details). The action receives the field tree and a `detail` object with `root` and `submitted` field trees, which is useful when submitting a sub-form.

After validation passes, the action itself may still fail due to scenarios such as a network error or duplicate entry. In those cases, you can surface the failure by returning the error(s). On the other hand, to indicate success, you only need to return `null` or `undefined`, or call an empty `return`.

## [Showing submission state with `submitting()`](#showing-submission-state-with-submitting)

When you need to track whether the form is in the process of submitting, Signal Forms provides a `submitting()` signal that returns `true` while the `action` function is running. Use it to show loading indicators or disable the submit button to prevent duplicate submissions.

```
<button type="submit" [disabled]="contactForm().submitting()">
  @if (contactForm().submitting()) {
    Sending...
  } @else {
    Send
  }
</button>
```

Once the `action` function succeeds or returns an error, the `submitting()` signal automatically resets back to `false`.

## [Managing submission errors](#managing-submission-errors)

### [Server errors](#server-errors)

When your `action` function communicates with a server, the server may return errors that need to appear on specific fields. Return these errors from the `action` to route them to their target fields.

#### [Errors on the submitted field](#errors-on-the-submitted-field)

By default, errors returned from the `action` are assigned to the submitted field (the field tree you passed to [`submit()`](/api/forms/signals/submit)):

```
action: async (field) => {
  const result = await saveContact(field().value());
  if (result.ok) return;

  return {kind: 'serverError', message: 'Failed to submit form'};
};
```

#### [Errors on specific fields](#errors-on-specific-fields)

When you want to route an error to a specific field, include a `fieldTree` property pointing to that field:

```
action: async (field) => {
  const result = await saveContact(field().value());
  if (result.ok) return;

  return {kind: 'taken', message: result.message, fieldTree: field.email};
};
```

#### [Multiple errors](#multiple-errors)

When you want to report errors on multiple fields, return an array:

```
action: async (field) => {
  const result = await registerUser(field().value());
  if (result.ok) return;

  return result.errors.map((err: {field: string; message: string}) => ({
    kind: 'serverError',
    message: err.message,
    fieldTree: field[err.field as keyof typeof field],
  }));
};
```

### [Auto-clearing submission errors](#auto-clearing-submission-errors)

Submission errors clear automatically when the user edits the field. If the `action` returns an error on the email field, that error disappears as soon as the user changes the email value.

This differs from validation errors, which recompute reactively. Validation rules run again on each change and may produce the same error. Submission errors are one-time results from the server — once cleared, they do not reappear unless the form is submitted again.

**TIP:** Submission errors appear alongside validation errors in the field's `errors()` signal. For guidance on displaying errors in your template, see the [Field State Management guide](guide/forms/signals/field-state-management).

## [Handling invalid submissions with `onInvalid`](#handling-invalid-submissions-with-oninvalid)

When validation fails, the `action` function does not run. If you need to respond to a failed submission attempt — such as scrolling to the first error, showing a toast, or focusing an invalid field — use the `onInvalid` callback.

```
contactForm = form(
  this.contactModel,
  (schemaPath) => {
    required(schemaPath.name);
    required(schemaPath.email);
  },
  {
    submission: {
      action: async (field) => {
        await saveContact(field().value());
      },
      onInvalid: (field) => {
        const firstError = field().errorSummary()[0];
        firstError?.fieldTree().focusBoundControl();
      },
    },
  },
);
```

The `onInvalid` callback receives the same `(field, detail)` parameters as `action`. It runs after all interactive fields are marked as touched, so validation errors are already visible in the UI when it executes.

## [Controlling validation gating with `ignoreValidators`](#controlling-validation-gating-with-ignorevalidators)

By default, [`submit()`](/api/forms/signals/submit) ignores pending validators. If no validators have failed, the action runs even if some async validators are still in progress. The `ignoreValidators` option gives you control over this behavior.

| Value       | Behavior                                                                 |
| ----------- | ------------------------------------------------------------------------ |
| `'pending'` | Submit if no validators have failed, even if some are pending (default)  |
| `'none'`    | Submit only if all validators pass — pending validators block submission |
| `'all'`     | Always submit regardless of validation state                             |

```
contactForm = form(
  this.contactModel,
  (schemaPath) => {
    required(schemaPath.name);
    required(schemaPath.email);
  },
  {
    submission: {
      action: async (field) => {
        await saveContact(field().value());
      },
      ignoreValidators: 'none',
    },
  },
);
```

Use `'none'` when your form has async validators (such as checking username availability) and you need all validation to complete before submitting. Use `'all'` for draft-saving scenarios where you want to persist data regardless of validation state.

## [Manual submission with `submit()`](#manual-submission-with-submit)

The [`FormRoot`](/api/forms/signals/FormRoot) directive is the most common way to trigger submission, but you can also call [`submit()`](/api/forms/signals/submit) directly. This is useful for multi-step wizards, auto-save, or triggering submission from outside the form element.

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, submit} from '@angular/forms/signals';

@Component({
  selector: 'app-contact',
  imports: [FormField],
  template: `
    <label>
      Name
      <input [formField]="contactForm.name" />
    </label>

    <label>
      Email
      <input type="email" [formField]="contactForm.email" />
    </label>

    <button (click)="onSave()">Save</button>
  `,
})
export class Contact {
  contactModel = signal({
    name: '',
    email: '',
  });

  contactForm = form(this.contactModel, (schemaPath) => {
    required(schemaPath.name);
    required(schemaPath.email);
  });

  async onSave() {
    // When calling `submit()` directly, you pass the action as the second argument
    // instead of configuring it in `FormOptions`.
    const success = await submit(this.contactForm, async (field) => {
      const result = await saveContact(field().value());
      if (result.ok) return;

      return {kind: 'serverError', message: 'Failed to save'};
    });

    if (success) {
      // Handle success — navigate, show confirmation, etc.
    }
  }
}
```

## [Handling side effects](#handling-side-effects)

The [`submit()`](/api/forms/signals/submit) function returns a `Promise<boolean>` — `true` when the action completes without errors, `false` when validation fails, the action returns errors, or a submission is already in progress. If the action throws, the promise rejects. Use this to trigger side effects like navigation or notifications.

```
async onSave() {
  const success = await submit(this.contactForm, async (field) => {
    await saveContact(field().value());
  });

  if (success) {
    await this.router.navigate(['/confirmation']);
  }
}
```

When the action produces data that a side effect needs, such as a server-generated ID, handle the side effect inside the action:

```
async onSave() {
  await submit(this.contactForm, async (field) => {
    const contact = await createContact(field().value());
    await this.router.navigate(['/confirmation', contact.id]);
  });
}
```

When using [`FormRoot`](/api/forms/signals/FormRoot), side effects also go inside the `action` since [`FormRoot`](/api/forms/signals/FormRoot) calls [`submit()`](/api/forms/signals/submit) internally:

```
submission: {
  action: async (field) => {
    const result = await saveContact(field().value());
    if (result.ok) {
      await this.router.navigate(['/confirmation']);
      return;
    }

    return {kind: 'serverError', message: 'Failed to submit form'};
  },
}
```

## [Concurrent submissions](#concurrent-submissions)

When a submission is in progress, subsequent calls to [`submit()`](/api/forms/signals/submit) for the same form or any of its descendants return `false` immediately without running the action. This prevents duplicate submissions and side effects if a user triggers the submit action multiple times quickly.

## [Next steps](#next-steps)

This guide covered submitting forms and handling form submission errors. Related guides explore other aspects of Signal Forms:

----
url: https://angular.dev/api/common/PRECONNECT_CHECK_BLOCKLIST
----

```
    
      const PRECONNECT_CHECK_BLOCKLIST: InjectionToken<(string | string[])[]>;
    
    
```

## [Description](#description)

Injection token to configure which origins should be excluded from the preconnect checks. It can either be a single string or an array of strings to represent a group of origins, for example:

```
{provide: PRECONNECT_CHECK_BLOCKLIST, useValue: 'https://your-domain.com'}
```

or:

```
{provide: PRECONNECT_CHECK_BLOCKLIST,
 useValue: ['https://your-domain-1.com', 'https://your-domain-2.com']}
```

Jump to details

----
url: https://angular.dev/api/common/http/HTTP_TRANSFER_CACHE_ORIGIN_MAP
----

```
    
      const HTTP_TRANSFER_CACHE_ORIGIN_MAP: InjectionToken<Record<string, string>>;
    
    
```

## [Description](#description)

If your application uses different HTTP origins to make API calls (via [`HttpClient`](/api/common/http/HttpClient)) on the server and on the client, the [`HTTP_TRANSFER_CACHE_ORIGIN_MAP`](/api/common/http/HTTP_TRANSFER_CACHE_ORIGIN_MAP) token allows you to establish a mapping between those origins, so that `HttpTransferCache` feature can recognize those requests as the same ones and reuse the data cached on the server during hydration on the client.

**IMPORTANT:** The [`HTTP_TRANSFER_CACHE_ORIGIN_MAP`](/api/common/http/HTTP_TRANSFER_CACHE_ORIGIN_MAP) token should *only* be provided in the *server* code of your application (typically in the `app.server.config.ts` script). Angular throws an error if it detects that the token is defined while running on the client.

## [Usage Notes](#usage-notes)

When the same API endpoint is accessed via `http://internal-domain.com:8080` on the server and via `https://external-domain.com` on the client, you can use the following configuration:

```
// in app.server.config.ts
{
    provide: HTTP_TRANSFER_CACHE_ORIGIN_MAP,
    useValue: {
        'http://internal-domain.com:8080': 'https://external-domain.com'
    }
}
```

Jump to details

----
url: https://angular.dev/api/aria/accordion/AccordionGroup
----

```
    
      class AccordionGroup implements OnDestroy {}
    
    
```

A reference to the group element.

The text direction (ltr or rtl).

Whether the entire accordion group is disabled.

Whether multiple accordion items can be expanded simultaneously.

Whether to allow disabled items to receive focus. When `true`, disabled items are focusable but not interactive. When `false`, disabled items are skipped during navigation.

Whether keyboard navigation should wrap around from the last item to the first, and vice-versa.

@returns`void`

Expands all accordion panels if multi-expandable.

@returns`void`

Collapses all accordion panels.

@returns`void`

## [Description](#description)

A container for a group of accordion items. It manages the overall state and interactions of the accordion, such as keyboard navigation and expansion mode.

The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels, coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it. It supports both single and multiple expansion modes.

```
<div ngAccordionGroup [multiExpandable]="true">
  <div class="accordion-item">
    <h3>
      <button ngAccordionTrigger [panel]="panel1">Item 1</button>
    </h3>
    <div ngAccordionPanel #panel1="ngAccordionPanel">
      <ng-template ngAccordionContent>
        <p>Content for Item 1.</p>
      </ng-template>
    </div>
  </div>
  <div class="accordion-item">
    <h3>
      <button ngAccordionTrigger [panel]="panel2">Item 2</button>
    </h3>
    <div ngAccordionPanel #panel2="ngAccordionPanel">
      <ng-template ngAccordionContent>
        <p>Content for Item 2.</p>
      </ng-template>
    </div>
  </div>
</div>
```

Jump to details

----
url: https://angular.dev/api/forms/signals/provideExperimentalWebMcpForms
----

```
function provideExperimentalWebMcpForms(): EnvironmentProviders;
```

@returns`EnvironmentProviders`

Jump to details

----
url: https://angular.dev/api/animations/AnimationStateMetadata
----

```
    
      interface AnimationStateMetadata extends AnimationMetadata {}
    
    
```

The state name, unique within the component.

The CSS styles associated with this state.

An options object containing developer-defined parameters that provide styling defaults and can be overridden on invocation.

Jump to details

----
url: https://angular.dev/api/animations/useAnimation
----

```
function useAnimation(
  animation: AnimationReferenceMetadata,
  options?: AnimationOptions | null,
): AnimationAnimateRefMetadata;
```

@deprecated

Use `animate.enter` or `animate.leave` instead. Intent to remove in v23

@paramanimation`AnimationReferenceMetadata`

The reusable animation to start.

@paramoptions`AnimationOptions | null`

An options object that can contain a delay value for the start of the animation, and additional override values for developer-defined parameters.

@returns`AnimationAnimateRefMetadata`

Jump to details

----
url: https://angular.dev/api/core/AnimationCallbackEvent
----

```
    
      type AnimationCallbackEvent = {target: Element; animationComplete: VoidFunction}
    
    
```

Jump to details

----
url: https://angular.dev/api/router/RoutesRecognized
----

```
    
      class RoutesRecognized extends RouterEvent {}
    
    
```

@paramid`number`

@paramurl`string`

@paramurlAfterRedirects`string`

@paramstate`RouterStateSnapshot`

@returns`RoutesRecognized`

A unique ID that the router assigns to every router navigation.

The URL that is the destination for this navigation.

Jump to details

----
url: https://angular.dev/tutorials/first-app
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/first-app/01-hello-world)

[](/tutorials/first-app/01-hello-world)

This tutorial consists of lessons that introduce the Angular concepts you need to know to start coding in Angular.

You can do as many or as few as you would like and you can do them in any order.

**HELPFUL:** Prefer video? We also have a full [YouTube course](https://youtube.com/playlist?list=PL1w1q3fL4pmj9k1FrJ3Pe91EPub2_h4jF\&si=1q9889ulHp8VZ0e7) for this tutorial!

[](https://www.youtube.com/watch?v=xAT0lHYhHMY\&autoplay=1)

## [Before you start](#before-you-start)

For the best experience with this tutorial, review these requirements to make sure you have what you need to be successful.

### [Your experience](#your-experience)

The lessons in this tutorial assume that you have experience with the following:

1. Created an HTML web page by editing the HTML directly.
2. Programmed web site content in JavaScript.
3. Read Cascading Style Sheet (CSS) content and understand how selectors are used.
4. Used command-line instructions to perform tasks on your computer.

### [Your equipment](#your-equipment)

These lessons can be completed using a local installation of the Angular tools or in our embedded editor. Local Angular development can be completed on Windows, macOS or Linux based systems.

**NOTE:** Look for alerts like this one, which call out steps that may only be for your local editor.

## [Conceptual preview of your first Angular app](#conceptual-preview-of-your-first-angular-app)

The lessons in this tutorial create an Angular app that lists houses for rent and shows the details of individual houses. This app uses features that are common to many Angular apps.

## [Local development environment](#local-development-environment)

**NOTE:** This step is only for your local environment!

Perform these steps in a command-line tool on the computer you want to use for this tutorial.

1. ### [Identify the version of `node.js` that Angular requires](#identify-the-version-of-nodejs-that-angular-requires)

   Angular requires an active LTS or maintenance LTS version of Node. Let's confirm your version of `node.js`. For information about specific version requirements, see the engines property in the [package.json file](https://unpkg.com/browse/@angular/core@15.1.5/package.json).

   From a **Terminal** window:

   1. Run the following command: `node --version`
   2. Confirm that the version number displayed meets the requirements.

2. ### [Install the correct version of `node.js` for Angular](#install-the-correct-version-of-nodejs-for-angular)

   If you do not have a version of `node.js` installed, please follow the [directions for installation on nodejs.org](https://nodejs.org/en/download/)

3. ### [Install the latest version of Angular](#install-the-latest-version-of-angular)

   With `node.js` and `npm` installed, the next step is to install the [Angular CLI](tools/cli) which provides tooling for effective Angular development.

   From a **Terminal** window run the following command: `npm install -g @angular/cli`.

4. ### [Install integrated development environment (IDE)](#install-integrated-development-environment-ide)

   You are free to use any tool you prefer to build apps with Angular. We recommend the following:

   1. [Visual Studio Code](https://code.visualstudio.com/)
   2. As an optional, but recommended step you can further improve your developer experience by installing the [Angular Language Service](https://marketplace.visualstudio.com/items?itemName=Angular.ng-template)
   3. [WebStorm](https://www.jetbrains.com/webstorm/)

5. ### [Optional: set-up your AI powered IDE](#optional-set-up-your-ai-powered-ide)

   In case you're following this tutorial in your preferred AI powered IDE, [check out Angular prompt rules and best practices](/ai/develop-with-ai).

For more information about the topics covered in this lesson, visit:

----
url: https://angular.dev/api/animations/AnimationAnimateRefMetadata
----

```
    
      interface AnimationAnimateRefMetadata extends AnimationMetadata {}
    
    
```

An animation reference object.

An options object containing a delay and developer-defined parameters that provide styling defaults and can be overridden on invocation. Default delay is 0.

Jump to details

----
url: https://angular.dev/api/forms/signals/AsyncValidationResult
----

```
    
      type AsyncValidationResult<E extends ValidationError = ValidationError> = ValidationResult<E> | 'pending'
    
    
```

## [Description](#description)

An asynchronous validation result where all errors explicitly define their target field.

The result may be one of the following:

1. A [`ValidationResult`](/api/forms/signals/ValidationResult) to indicate the result if resolved.
2. 'pending' if the validation is not yet resolved.

Jump to details

----
url: https://angular.dev/api/forms/signals/compat/extractValue
----

```
function extractValue<T>(field: FieldTree<T>): RawValue<T>;
function extractValue<T>(
  field: FieldTree<T>,
  filter: ExtractFilter,
): DeepPartial<RawValue<T>>;
```

Utility to unwrap a [`FieldTree`](/api/forms/signals/FieldTree) into its underlying raw value.

This function is recursive, so if the field tree represents an object or an array, the result will be an object or an array of the raw values of its children.

@paramfield`FieldTree<T>`

The field tree to extract the value from.

@returns`RawValue<T>`

The raw value of the field tree.

Utility to unwrap a [`FieldTree`](/api/forms/signals/FieldTree) into its underlying raw value.

This function is recursive, so if the field tree represents an object or an array, the result will be an object or an array of the raw values of its children.

@paramfield`FieldTree<T>`

The field tree to extract the value from.

@paramfilter`ExtractFilter`

Criteria to include only fields matching certain state (dirty, touched, enabled).

@returns`DeepPartial<RawValue<T>>`

A partial value containing only the fields matching the filter, or `undefined` if none match.

## [Description](#description)

Utility to unwrap a [`FieldTree`](/api/forms/signals/FieldTree) into its underlying raw value.

This function is recursive, so if the field tree represents an object or an array, the result will be an object or an array of the raw values of its children.

Jump to details

----
url: https://angular.dev/api/router/destroyDetachedRouteHandle
----

```
function destroyDetachedRouteHandle(handle: DetachedRouteHandle): void;
```

@paramhandle`DetachedRouteHandle`

The detached route handle to destroy.

@returns`void`

## [Description](#description)

Destroys the component associated with a [`DetachedRouteHandle`](/api/router/DetachedRouteHandle).

This function should be used when a [`RouteReuseStrategy`](/api/router/RouteReuseStrategy) decides to drop a stored handle and wants to ensure that the component is destroyed.

Jump to details

----
url: https://angular.dev/cli/generate/application
----

```
ng generate application [name] [options]
```

```
ng generate app [name] [options]
```

Generates a new Angular application within your workspace. This schematic sets up the foundational structure of your project, including the root component, module, and configuration files. You can customize various aspects of the application, such as routing, styling, and testing.

## [Arguments](#arguments)

`name`

The name for the new application. This name will be used for the project directory and various identifiers throughout the application's code.

Value Type`string`

## [Options](#options)

`file-name-style-guide`

The file naming convention to use for generated files. The '2025' style guide (default) uses a concise format (e.g., `app.ts` for the root component), while the '2016' style guide includes the type in the file name (e.g., `app.component.ts`). For more information, see the Angular Style Guide ([https://angular.dev/style-guide](/style-guide)).

Value Type`string`Allowed Values`2016`, `2025`Default`2025`

`inline-style`

Alias`s`

Include the styles for the root component directly within the `app.ts` file. Only CSS styles can be included inline. By default, a separate stylesheet file (e.g., `app.css`) is created.

Value Type`boolean`

`inline-template`

Alias`t`

Include the HTML template for the root component directly within the `app.ts` file. By default, a separate template file (e.g., `app.html`) is created.

Value Type`boolean`

`minimal`

Generate a minimal project without any testing frameworks. This is intended for learning purposes and simple experimentation, not for production applications.

Value Type`boolean`Default`false`

`prefix`

Alias`p`

A prefix to be added to the selectors of components generated within this application. For example, if the prefix is `my-app` and you generate a component named `my-component`, the selector will be `my-app-my-component`.

Value Type`string`Default`app`

`project-root`

The directory where the new application's files will be created, relative to the workspace root. If not specified, the application will be created in a subfolder within the `projects` directory, using the application's name.

Value Type`string`

`routing`

Generate an application with routing already configured. This sets up the necessary files and modules for managing navigation between different views in your application.

Value Type`boolean`Default`true`

`skip-install`

Skip the automatic installation of packages. You will need to manually install the dependencies later.

Value Type`boolean`Default`false`

`skip-package-json`

Do not add dependencies to the `package.json` file.

Value Type`boolean`Default`false`

`skip-tests`

Alias`S`

Skip the generation of a unit test files `spec.ts`.

Value Type`boolean`Default`false`

`ssr`

Configure the application for Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering).

Value Type`boolean`Default`false`

`standalone`

Create an application that utilizes the standalone API, eliminating the need for NgModules. This can simplify the structure of your application.

Value Type`boolean`Default`true`

`strict`

Enable stricter bundle budget settings for the application. This helps to keep your application's bundle size small and improve performance. For more information, see [https://angular.dev/tools/cli/template-typecheck#strict-mode](/tools/cli/template-typecheck#strict-mode)

Value Type`boolean`Default`true`

`style`

The type of stylesheet files to be created for components in the application.

Value Type`string`Allowed Values`css`, `less`, `sass`, `scss`, `tailwind`Default`css`

`test-runner`

The unit testing runner to use.

Value Type`string`Allowed Values`karma`, `vitest`Default`vitest`

`view-encapsulation`

Sets the view encapsulation mode for the application's components. This determines how component styles are scoped and applied.

Value Type`string`Allowed Values`Emulated`, `None`, `ShadowDom`

`zoneless`

Generate an application that does not use `zone.js`.

Value Type`boolean`Default`true`

Jump to details

----
url: https://angular.dev/guide/aria/autocomplete
----

An accessible input field that filters and suggests options as users type, helping them find and select values from a list.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-basic"], app-root:not([theme])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-material"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="material-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.material-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 3rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

.option[aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 50%;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.clear-button:hover {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.clear-button:focus-visible {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 15%, transparent);
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-retro"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="retro-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon" aria-hidden="true">X</span>
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;

  display: flex;
  justify-content: center;
  font-size: 0.6rem;
  font-family: 'Press Start 2P';

  --retro-button-color: #fff;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.retro-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--retro-button-color);
}

.retro-autocomplete:focus-within {
  box-shadow: var(--retro-pressed-shadow);
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: #000;
  z-index: 1;
}

.autocomplete-input {
  width: 15rem;
  font-size: 0.6rem;
  border-radius: 0;
  font-family: 'Press Start 2P';
  word-spacing: -5px;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: #000;
  border: none;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}

.autocomplete-input::placeholder {
  color: #000;
  opacity: 0.7;
}

.popup {
  width: 100%;
  margin-top: 20px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--win95-gray);
  color: var(--win95-shadow);
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  cursor: pointer;
  padding: 0;
  margin: 0;
  outline: none;
  font-family: 'Press Start 2P';
  font-size: 0.6rem;
  user-select: none;
}

.clear-button:hover {
  background: #e0e0e0;
  outline: 1px dashed var(--win95-dark-gray);
  outline-offset: 1px;
}

.clear-button:active {
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
}

.clear-button:focus-visible {
  outline: 2px dashed var(--hot-pink);
  outline-offset: 4px;
}

.clear-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
```

## [Usage](#usage)

Autocomplete works best when users need to select from a large set of options where typing is faster than scrolling. Consider using autocomplete when:

* **The option list is long** (more than 20 items) - Typing narrows down choices faster than scrolling through a dropdown
* **Users know what they're looking for** - They can type part of the expected value (like a state name, product, or username)
* **Options follow predictable patterns** - Users can guess partial matches (like country codes, email domains, or categories)
* **Speed matters** - Forms benefit from quick selection without extensive navigation

Avoid autocomplete when:

* The list has fewer than 10 options - A regular dropdown or radio group provides better visibility
* Users need to browse options - If discovery is important, show all options upfront
* Options are unfamiliar - Users can't type what they don't know exists in the list

## [Features](#features)

Angular's autocomplete provides a fully accessible combobox implementation with:

* **Keyboard Navigation** - Navigate options with arrow keys, select with Enter, close with Escape
* **Screen Reader Support** - Built-in ARIA attributes for assistive technologies
* **Dynamic Highlight Behavior** - Built-in support for inline selection suggestions
* **Signal-Based Reactivity** - Reactive state management using Angular signals
* **Popover API Integration** - Leverages the native HTML Popover API for optimal positioning
* **Bidirectional Text Support** - Automatically handles right-to-left (RTL) languages

## [Examples](#examples)

### [Auto-select mode](#auto-select-mode)

Users typing partial text expect immediate confirmation that their input matches an available option. Auto-select mode updates the input value to match the first filtered option as users type, reducing the number of keystrokes needed and providing instant feedback that their search is on the right track.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-basic"], app-root:not([theme])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-material"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="material-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.material-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 3rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

.option[aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 50%;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.clear-button:hover {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.clear-button:focus-visible {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 15%, transparent);
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="basic-retro"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    this.commitSelection();
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  private commitSelection() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
      this.selectedOption.set([]);
    }
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="retro-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon" aria-hidden="true">X</span>
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;

  display: flex;
  justify-content: center;
  font-size: 0.6rem;
  font-family: 'Press Start 2P';

  --retro-button-color: #fff;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.retro-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--retro-button-color);
}

.retro-autocomplete:focus-within {
  box-shadow: var(--retro-pressed-shadow);
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: #000;
  z-index: 1;
}

.autocomplete-input {
  width: 15rem;
  font-size: 0.6rem;
  border-radius: 0;
  font-family: 'Press Start 2P';
  word-spacing: -5px;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: #000;
  border: none;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}

.autocomplete-input::placeholder {
  color: #000;
  opacity: 0.7;
}

.popup {
  width: 100%;
  margin-top: 20px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--win95-gray);
  color: var(--win95-shadow);
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  cursor: pointer;
  padding: 0;
  margin: 0;
  outline: none;
  font-family: 'Press Start 2P';
  font-size: 0.6rem;
  user-select: none;
}

.clear-button:hover {
  background: #e0e0e0;
  outline: 1px dashed var(--win95-dark-gray);
  outline-offset: 1px;
}

.clear-button:active {
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
}

.clear-button:focus-visible {
  outline: 2px dashed var(--hot-pink);
  outline-offset: 4px;
}

.clear-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
```

### [Manual selection mode](#manual-selection-mode)

Manual selection mode keeps the typed text unchanged while users navigate the suggestion list, preventing confusion from automatic updates. The input only changes when users explicitly confirm their choice with Enter or a click.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="manual-basic"], app-root:not([theme])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="manual-material"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="material-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.material-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 3rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

.option[aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 50%;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.clear-button:hover {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.clear-button:focus-visible {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 15%, transparent);
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="manual-retro"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="retro-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon" aria-hidden="true">X</span>
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;

  display: flex;
  justify-content: center;
  font-size: 0.6rem;
  font-family: 'Press Start 2P';

  --retro-button-color: #fff;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.retro-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--retro-button-color);
}

.retro-autocomplete:focus-within {
  box-shadow: var(--retro-pressed-shadow);
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: #000;
  z-index: 1;
}

.autocomplete-input {
  width: 15rem;
  font-size: 0.6rem;
  border-radius: 0;
  font-family: 'Press Start 2P';
  word-spacing: -5px;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: #000;
  border: none;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}

.autocomplete-input::placeholder {
  color: #000;
  opacity: 0.7;
}

.popup {
  width: 100%;
  margin-top: 20px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--win95-gray);
  color: var(--win95-shadow);
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  cursor: pointer;
  padding: 0;
  margin: 0;
  outline: none;
  font-family: 'Press Start 2P';
  font-size: 0.6rem;
  user-select: none;
}

.clear-button:hover {
  background: #e0e0e0;
  outline: 1px dashed var(--win95-dark-gray);
  outline-offset: 1px;
}

.clear-button:active {
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
}

.clear-button:focus-visible {
  outline: 2px dashed var(--hot-pink);
  outline-offset: 4px;
}

.clear-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
```

### [Highlight mode](#highlight-mode)

Highlight mode allows the user to navigate options with arrow keys without changing the input value as they browse until they explicitly select a new option with Enter or click.

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="highlight-basic"], app-root:not([theme])',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
    this.navigated.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);
  navigated = signal(false);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });

    effect(() => {
      if (!this.popupExpanded()) {
        this.navigated.set(false);
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
      (keydown.arrowdown)="navigated.set(true)"
      (keydown.arrowup)="navigated.set(true)"
      [inlineSuggestion]="query() || navigated() ? selectedOption()[0] : undefined"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="highlight-material"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
    this.navigated.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);
  navigated = signal(false);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });

    effect(() => {
      if (!this.popupExpanded()) {
        this.navigated.set(false);
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="material-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
      (keydown.arrowdown)="navigated.set(true)"
      (keydown.arrowup)="navigated.set(true)"
      [inlineSuggestion]="query() || navigated() ? selectedOption()[0] : undefined"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: flex;
  justify-content: center;
  font-family: var(--inter-font);
  --primary: var(--hot-pink);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.material-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 3rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 2rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 3rem;
  border-radius: 3rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--primary);
}

.option[aria-selected='true'] {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 50%;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}

.clear-button:hover {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
}

.clear-button:focus-visible {
  color: var(--primary);
  background-color: color-mix(in srgb, var(--primary) 15%, transparent);
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}
```

### app.ts

```
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';
import {afterRenderEffect, Component, computed, effect, signal, viewChild} from '@angular/core';
import {FormsModule} from '@angular/forms';

@Component({
  selector: 'app-root[theme="highlight-retro"]',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule, FormsModule],
})
export class App {
  clear() {
    this.query.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
    this.navigated.set(false);
  }

  readonly listbox = viewChild(Listbox);
  readonly combobox = viewChild(Combobox);

  popupExpanded = signal(false);
  query = signal('');
  selectedOption = signal<string[]>([]);
  navigated = signal(false);

  countries = computed(() =>
    ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())),
  );

  constructor() {
    afterRenderEffect(() => {
      if (this.combobox()?.expanded() === true) {
        this.listbox()?.scrollActiveItemIntoView();
      }
    });

    effect(() => {
      if (!this.popupExpanded()) {
        this.navigated.set(false);
      }
    });
  }

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.query.set(selected[0]);
    } else {
      this.query.set('');
    }
    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }
}

const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### app.html

```
<div class="autocomplete-container">
  <div #origin class="retro-autocomplete">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      placeholder="Select a country"
      [(value)]="query"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
      (keydown.arrowdown)="navigated.set(true)"
      (keydown.arrowup)="navigated.set(true)"
      [inlineSuggestion]="query() || navigated() ? selectedOption()[0] : undefined"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon" aria-hidden="true">X</span>
    </button>
  </div>

  <div aria-live="polite" class="cdk-visually-hidden">
    {{ countries().length === 0 ? 'No results found for ' + query() : '' }}
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (countries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of countries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>
</div>
```

### app.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

:host {
  --win95-gray: #c0c0c0;
  --win95-dark-gray: #808080;
  --win95-light: #ffffff;
  --win95-shadow: #000000;

  display: flex;
  justify-content: center;
  font-size: 0.6rem;
  font-family: 'Press Start 2P';

  --retro-button-color: #fff;
  --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff);
  --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000);
  --retro-elevated-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-light),
    inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast);
  --retro-flat-shadow:
    4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast),
    -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast);
  --retro-pressed-shadow:
    inset 4px 4px 0px 0px var(--retro-shadow-dark),
    inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast),
    0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast),
    0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.retro-autocomplete {
  display: flex;
  position: relative;
  align-items: center;
  box-shadow: var(--retro-flat-shadow);
  background-color: var(--retro-button-color);
}

.retro-autocomplete:focus-within {
  box-shadow: var(--retro-pressed-shadow);
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: #000;
  z-index: 1;
}

.autocomplete-input {
  width: 15rem;
  font-size: 0.6rem;
  border-radius: 0;
  font-family: 'Press Start 2P';
  word-spacing: -5px;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: #000;
  border: none;
  background-color: transparent;
  box-shadow: none;
  outline: none;
}

.autocomplete-input::placeholder {
  color: #000;
  opacity: 0.7;
}

.popup {
  width: 100%;
  margin-top: 20px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0;
  background-color: var(--septenary-contrast);
  box-shadow: var(--retro-flat-shadow);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px dashed var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--win95-gray);
  color: var(--win95-shadow);
  border-style: solid;
  border-width: 1px;
  border-color: var(--win95-light) var(--win95-dark-gray) var(--win95-dark-gray) var(--win95-light);
  box-shadow: 1px 1px 0 var(--win95-shadow);
  cursor: pointer;
  padding: 0;
  margin: 0;
  outline: none;
  font-family: 'Press Start 2P';
  font-size: 0.6rem;
  user-select: none;
}

.clear-button:hover {
  background: #e0e0e0;
  outline: 1px dashed var(--win95-dark-gray);
  outline-offset: 1px;
}

.clear-button:active {
  border-color: var(--win95-dark-gray) var(--win95-light) var(--win95-light) var(--win95-dark-gray);
  box-shadow: 1px 1px 0 var(--win95-shadow) inset;
}

.clear-button:focus-visible {
  outline: 2px dashed var(--hot-pink);
  outline-offset: 4px;
}

.clear-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
```

### [Signal Forms Integration](#signal-forms-integration)

Angular Aria integrates seamlessly with the signal-based [Signal Forms](guide/forms/signals/overview) API. You can encapsulate complex inputs into reusable custom control components implementing [`FormValueControl`](/api/forms/signals/FormValueControl).

The following example demonstrates a country selector component implementing `FormValueControl<string>`, bound to the parent form using `[formField]` and protected by schema validation rules.

### app.ts

```
import {Component, signal} from '@angular/core';
import {form, FormField, required, validate} from '@angular/forms/signals';
import {ALL_COUNTRIES, CountrySelector} from './country-selector';

@Component({
  selector: 'app-root',
  templateUrl: 'app.html',
  styleUrl: 'app.css',
  imports: [CountrySelector, FormField],
})
export class App {
  // Signal Forms setup
  model = signal({country: ''});
  countryForm = form(this.model, (p) => {
    required(p.country, {message: 'Country selection is required'});

    validate(p.country, (ctx) => {
      const value = ctx.value();
      if (value && !ALL_COUNTRIES.includes(value)) {
        return {
          kind: 'invalidCountry',
          message: 'Please select a valid country.',
        };
      }
      return null;
    });
  });
}
```

### app.html

```
<div class="form-container">
  <country-selector [formField]="countryForm.country"></country-selector>
</div>
```

### country-selector.ts

```
import {Component, computed, input, model, output, signal, viewChild} from '@angular/core';
import {FormValueControl, ValidationError} from '@angular/forms/signals';
import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox';
import {Listbox, Option} from '@angular/aria/listbox';
import {OverlayModule} from '@angular/cdk/overlay';

@Component({
  selector: 'country-selector',
  templateUrl: 'country-selector.html',
  styleUrl: 'country-selector.css',
  imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule],
})
export class CountrySelector implements FormValueControl<string> {
  readonly combobox = viewChild(Combobox);

  // FormValueControl implementation
  readonly value = model.required<string>();
  readonly touched = input<boolean>(false);
  readonly invalid = input<boolean>(false);
  readonly errors = input<readonly ValidationError.WithOptionalFieldTree[]>([]);
  readonly touch = output<void>();

  popupExpanded = signal(false);
  selectedOption = signal<string[]>([]);

  filteredCountries = computed(() => {
    const query = this.value()?.toLowerCase() ?? '';
    return ALL_COUNTRIES.filter((c) => c.toLowerCase().startsWith(query));
  });

  onCommit() {
    const selected = this.selectedOption();
    if (selected.length > 0) {
      this.value.set(selected[0]);
    }

    // Notify the parent form field directive that this control has been interacted with
    this.touch.emit();

    this.popupExpanded.set(false);
    this.combobox()?.element.focus();
  }

  clear() {
    this.value.set('');
    this.selectedOption.set([]);
    this.popupExpanded.set(false);
  }
}

export const ALL_COUNTRIES = [
  'Afghanistan',
  'Albania',
  'Algeria',
  'Andorra',
  'Angola',
  'Antigua and Barbuda',
  'Argentina',
  'Armenia',
  'Australia',
  'Austria',
  'Azerbaijan',
  'Bahamas',
  'Bahrain',
  'Bangladesh',
  'Barbados',
  'Belarus',
  'Belgium',
  'Belize',
  'Benin',
  'Bhutan',
  'Bolivia',
  'Bosnia and Herzegovina',
  'Botswana',
  'Brazil',
  'Brunei',
  'Bulgaria',
  'Burkina Faso',
  'Burundi',
  'Cabo Verde',
  'Cambodia',
  'Cameroon',
  'Canada',
  'Central African Republic',
  'Chad',
  'Chile',
  'China',
  'Colombia',
  'Comoros',
  'Congo (Congo-Brazzaville)',
  'Costa Rica',
  "Côte d'Ivoire",
  'Croatia',
  'Cuba',
  'Cyprus',
  'Czechia (Czech Republic)',
  'Democratic Republic of the Congo',
  'Denmark',
  'Djibouti',
  'Dominica',
  'Dominican Republic',
  'Ecuador',
  'Egypt',
  'El Salvador',
  'Equatorial Guinea',
  'Eritrea',
  'Estonia',
  'Eswatini (fmr. "Swaziland")',
  'Ethiopia',
  'Fiji',
  'Finland',
  'France',
  'Gabon',
  'Gambia',
  'Georgia',
  'Germany',
  'Ghana',
  'Greece',
  'Grenada',
  'Guatemala',
  'Guinea',
  'Guinea-Bissau',
  'Guyana',
  'Haiti',
  'Holy See',
  'Honduras',
  'Hungary',
  'Iceland',
  'India',
  'Indonesia',
  'Iran',
  'Iraq',
  'Ireland',
  'Israel',
  'Italy',
  'Jamaica',
  'Japan',
  'Jordan',
  'Kazakhstan',
  'Kenya',
  'Kiribati',
  'Kuwait',
  'Kyrgyzstan',
  'Laos',
  'Latvia',
  'Lebanon',
  'Lesotho',
  'Liberia',
  'Libya',
  'Liechtenstein',
  'Lithuania',
  'Luxembourg',
  'Madagascar',
  'Malawi',
  'Malaysia',
  'Maldives',
  'Mali',
  'Malta',
  'Marshall Islands',
  'Mauritania',
  'Mauritius',
  'Mexico',
  'Micronesia',
  'Moldova',
  'Monaco',
  'Mongolia',
  'Montenegro',
  'Morocco',
  'Mozambique',
  'Myanmar (formerly Burma)',
  'Namibia',
  'Nauru',
  'Nepal',
  'Netherlands',
  'New Zealand',
  'Nicaragua',
  'Niger',
  'Nigeria',
  'North Korea',
  'North Macedonia',
  'Norway',
  'Oman',
  'Pakistan',
  'Palau',
  'Palestine State',
  'Panama',
  'Papua New Guinea',
  'Paraguay',
  'Peru',
  'Philippines',
  'Poland',
  'Portugal',
  'Qatar',
  'Romania',
  'Russia',
  'Rwanda',
  'Saint Kitts and Nevis',
  'Saint Lucia',
  'Saint Vincent and the Grenadines',
  'Samoa',
  'San Marino',
  'Sao Tome and Principe',
  'Saudi Arabia',
  'Senegal',
  'Serbia',
  'Seychelles',
  'Sierra Leone',
  'Singapore',
  'Slovakia',
  'Slovenia',
  'Solomon Islands',
  'Somalia',
  'South Africa',
  'South Korea',
  'South Sudan',
  'Spain',
  'Sri Lanka',
  'Sudan',
  'Suriname',
  'Sweden',
  'Switzerland',
  'Syria',
  'Tajikistan',
  'Tanzania',
  'Thailand',
  'Timor-Leste',
  'Togo',
  'Tonga',
  'Trinidad and Tobago',
  'Tunisia',
  'Türkiye',
  'Turkmenistan',
  'Tuvalu',
  'Uganda',
  'Ukraine',
  'United Arab Emirates',
  'United Kingdom',
  'United States of America',
  'Uruguay',
  'Uzbekistan',
  'Vanuatu',
  'Venezuela',
  'Vietnam',
  'Yemen',
  'Zambia',
  'Zimbabwe',
];
```

### country-selector.html

```
<div class="autocomplete-container">
  <div #origin class="autocomplete-input-container">
    <span class="search-icon material-symbols-outlined" translate="no" aria-hidden="true"
      >search</span
    >
    <input
      #combobox="ngCombobox"
      ngCombobox
      class="autocomplete-input"
      [class.invalid]="touched() && invalid()"
      placeholder="Select a country"
      [value]="value()"
      (input)="value.set($any($event.target).value)"
      [(expanded)]="popupExpanded"
      (click)="popupExpanded.set(true)"
    />
    <button
      type="button"
      class="clear-button"
      aria-label="Clear"
      (mousedown)="$event.preventDefault()"
      (click)="clear()"
    >
      <span class="clear-icon material-symbols-outlined" translate="no" aria-hidden="true"
        >close</span
      >
    </button>
  </div>

  <ng-template
    [cdkConnectedOverlay]="{origin, usePopover: 'inline', matchWidth: true}"
    [cdkConnectedOverlayOpen]="popupExpanded()"
  >
    <ng-template ngComboboxPopup [combobox]="combobox">
      <div class="popup">
        @if (filteredCountries().length === 0) {
          <div class="no-results">No results found</div>
        }
        <div
          #listbox="ngListbox"
          ngListbox
          ngComboboxWidget
          class="listbox"
          focusMode="activedescendant"
          selectionMode="explicit"
          [tabindex]="-1"
          [activeDescendant]="listbox.activeDescendant()"
          [(value)]="selectedOption"
          (click)="onCommit()"
          (keydown.enter)="onCommit()"
        >
          @for (country of filteredCountries(); track country) {
            <div class="option" ngOption [value]="country" [label]="country">
              <span class="option-label">{{ country }}</span>
              <span class="check-icon material-symbols-outlined" translate="no" aria-hidden="true"
                >check</span
              >
            </div>
          }
        </div>
      </div>
    </ng-template>
  </ng-template>

  @if (touched() && invalid() && errors().length > 0) {
    <div class="error-message">
      <span class="material-symbols-outlined" translate="no" aria-hidden="true">error</span>
      {{ errors()[0].message }}
    </div>
  }
</div>
```

### country-selector.css

```
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');

:host {
  display: block;
  font-family: var(--inter-font);
}

.autocomplete-container {
  display: flex;
  flex-direction: column;
  position: relative;
}

.autocomplete-input-container {
  display: flex;
  position: relative;
  align-items: center;
}

.material-symbols-outlined {
  font-size: 1.25rem;
  pointer-events: none;
}

.search-icon {
  left: 0.75rem;
  position: absolute;
  color: var(--quaternary-contrast);
}

.autocomplete-input {
  width: 13rem;
  font-size: 1rem;
  border-radius: 0.25rem;
  padding: 0.75rem 2.5rem 0.75rem 2.5rem;
  color: var(--primary-contrast);
  outline: none;
  border: 1px solid var(--quinary-contrast);
  background-color: var(--page-background);
}

.autocomplete-input:focus-visible {
  border-color: var(--hot-pink);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--hot-pink) 20%, transparent);
}

.autocomplete-input::placeholder {
  color: var(--quaternary-contrast);
}

.popup {
  width: 100%;
  margin-top: 8px;
  padding: 0.5rem;
  max-height: 11rem;
  border-radius: 0.5rem;
  background-color: var(--septenary-contrast);
  font-size: 0.9rem;
  box-shadow:
    0 4px 6px -1px rgba(0, 0, 0, 0.1),
    0 2px 4px -1px rgba(0, 0, 0, 0.06);
}

.no-results {
  padding: 1rem;
}

.listbox {
  gap: 2px;
  height: 100%;
  display: flex;
  overflow: auto;
  flex-direction: column;
  outline: none;
}

.option {
  display: flex;
  cursor: pointer;
  align-items: center;
  margin: 1px;
  padding: 0 1rem;
  min-height: 2.25rem;
  border-radius: 0.5rem;
  outline: none;
}

.option:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.option[data-active='true'] {
  outline-offset: -2px;
  outline: 2px solid var(--hot-pink);
}

.option[aria-selected='true'] {
  color: var(--hot-pink);
  background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent);
}

.option:not([aria-selected='true']) .check-icon {
  display: none;
}

.option-label {
  flex: 1;
}

.check-icon {
  font-size: 0.9rem;
}

.clear-button {
  position: absolute;
  right: 0.75rem;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background-color: transparent;
  border-radius: 0.25rem;
  cursor: pointer;
  color: var(--quaternary-contrast);
  padding: 0;
  margin: 0;
  outline: none;
  transition: background-color 0.15s ease;
}

.clear-button:hover {
  background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent);
}

.clear-button:focus-visible {
  outline: 2px solid var(--hot-pink);
  outline-offset: -2px;
}

.clear-icon {
  font-size: 1.25rem;
}

.error-message {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--error-color, #e15241);
  font-size: 0.85rem;
  margin-top: 6px;
}

.error-message span {
  font-size: 1rem;
}

.autocomplete-input.invalid {
  border-color: var(--error-color, #e15241);
}
```

### app.css

```
:host {
  display: flex;
  justify-content: center;
}
```

## [Testing](#testing)

The autocomplete pattern can be tested using a combination of `ComboboxHarness` and `ListboxHarness` from `@angular/aria/combobox/testing` and `@angular/aria/listbox/testing`. Here is an example of how to use the harnesses to test an autocomplete component:

```
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {ComboboxHarness} from '@angular/aria/combobox/testing';
import {ListboxHarness} from '@angular/aria/listbox/testing';
import {MyAutocompleteComponent} from './my-autocomplete'; // Your component

describe('MyAutocompleteComponent', () => {
  let fixture: ComponentFixture<MyAutocompleteComponent>;
  let loader: HarnessLoader;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyAutocompleteComponent],
    });

    fixture = TestBed.createComponent(MyAutocompleteComponent);
    await fixture.whenStable();
    loader = TestbedHarnessEnvironment.loader(fixture);
  });

  it('should filter options based on input', async () => {
    const combobox = await loader.getHarness(ComboboxHarness);

    // Type in the input to trigger filtering
    await combobox.setValue('ap');
    expect(await combobox.isOpen()).toBe(true);

    // Get the listbox harness from the popup
    const listbox = await combobox.getPopupWidget(ListboxHarness);
    const options = await listbox.getOptions();

    // Verify options are filtered (e.g., 'Apple', 'Apricot')
    expect(options.length).toBe(2);
    expect(await options[0].getText()).toBe('Apple');

    // Select the first option
    await options[0].click();

    // Verify the input value is updated and popup is closed
    expect(await combobox.isOpen()).toBe(false);
    expect(await combobox.getValue()).toBe('Apple');
  });
});
```

----
url: https://angular.dev/api/core/Sanitizer
----

```
    
      abstract class Sanitizer {}
    
    
```

@paramcontext`SecurityContext`

@paramvalue`string | {} | null`

@returns`string | null`

Jump to details

----
url: https://angular.dev/api/core/onIdle
----

```
function onIdle(
  options?: { timeout?: number | undefined } | undefined,
): Promise<void>;
```

@paramoptions`{ timeout?: number | undefined; } | undefined`

@returns`Promise<void>`

## [Description](#description)

A [`PrefetchTrigger`](/api/core/PrefetchTrigger) helper function to provide the logic of triggering dependency loading when the browser becomes idle.

Internally delegates to the configured [`IdleService`](/api/core/IdleService), whose default implementation uses [`requestIdleCallback`](https://developer.mozilla.org/docs/Web/API/Window/requestIdleCallback) when available and falls back to `setTimeout` otherwise. The default behavior can be replaced with [`provideIdleServiceWith`](/api/core/provideIdleServiceWith).

## [Usage Notes](#usage-notes)

```
injectAsync(import(...), {prefetch: onIdle})

// or with custom idle options:
injectAsync(import(...), {prefetch: () => onIdle({timeout: 100})})
```

Jump to details

----
url: https://angular.dev/api/forms/StatusChangeEvent
----

```
    
      class StatusChangeEvent extends ControlEvent {}
    
    
```

@paramstatus`FormControlStatus`

@paramsource`AbstractControl<any, any, any>`

@returns`StatusChangeEvent`

Jump to details

----
url: https://angular.dev/tutorials/learn-angular/20-inject-based-di
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

[](/tutorials/learn-angular/19-creating-an-injectable-service)[](/tutorials/learn-angular/22-pipes)

[](/tutorials/learn-angular/19-creating-an-injectable-service)[](/tutorials/learn-angular/22-pipes)

Creating an injectable service is the first part of the dependency injection (DI) system in Angular. How do you inject a service into a component? Angular has a convenient function called `inject()` that can be used in the proper context.

**NOTE:** Injection contexts are beyond the scope of this tutorial, but you can learn more in the [dependency injection (DI) essentials guide](/essentials/dependency-injection) and [DI context guide](guide/di/dependency-injection-context).

In this activity, you'll learn how to inject a service and use it in a component.

***

It is often helpful to initialize class properties with values provided by the DI system. Here's an example:

```
@Component({...})
class PetCareDashboard {
  petRosterService = inject(PetRosterService);
}
```

1. ### [Inject the `CarService`](#inject-the-carservice)

   In `app.ts`, using the `inject()` function inject the `CarService` and assign it to a property called `carService`

   **NOTE:** Notice the difference between the property `carService` and the class `CarService`.

2. ### [Use the `carService` instance](#use-the-carservice-instance)

   Calling `inject(CarService)` gave you an instance of the `CarService` that you can use in your application, stored in the `carService` property.

   Initialize the `display` property with the following implementation:

   ```
   display = this.carService.getCars().join(' ⭐️ ');
   ```

3. ### [Update the `App` template](#update-the-app-template)

   Update the component template in `app.ts` with the following code:

   ```
   template: `<p>Car Listing: {{ display }}</p>`,
   ```

You've just injected your first service into a component - fantastic effort.

----
url: https://angular.dev/api/core/ComponentMirror
----

```
    
      interface ComponentMirror<C> {}
    
    
```

The component's HTML selector.

The type of component the factory will create.

The inputs of the component.

The outputs of the component.

Selector for all elements in the component.

Whether this component is marked as standalone. Note: an extra flag, not present in `ComponentFactory`.

Jump to details

----
url: https://angular.dev/api/forms/signals/FieldContext
----

```
    
      type FieldContext<TValue, TPathKind extends PathKind = PathKind.Root> = TPathKind extends PathKind.Item  ? ItemFieldContext<TValue>  : TPathKind extends PathKind.Child    ? ChildFieldContext<TValue>    : RootFieldContext<TValue>
    
    
```

Jump to details

----
url: https://angular.dev/api/service-worker/VersionDetectedEvent
----

```
    
      interface VersionDetectedEvent {}
    
    
```

Jump to details

----
url: https://angular.dev/reference/configs/angular-compiler-options
----

When you use [ahead-of-time compilation (AOT)](tools/cli/aot-compiler), you can control how your application is compiled by specifying Angular compiler options in the [TypeScript configuration file](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).

The Angular options object, `angularCompilerOptions`, is a sibling to the `compilerOptions` object that supplies standard options to the TypeScript compiler.

### tsconfig.json

```
{
  "compileOnSave": false,
  "compilerOptions": {
    // ...
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    // ...
  }
}
```

## [Configuration inheritance with `extends`](#configuration-inheritance-with-extends)

Like the TypeScript compiler, the Angular AOT compiler also supports `extends` in the `angularCompilerOptions` section of the TypeScript configuration file. The `extends` property is at the top level, parallel to `compilerOptions` and `angularCompilerOptions`.

A TypeScript configuration can inherit settings from another file using the `extends` property. The configuration options from the base file are loaded first, then overridden by those in the inheriting configuration file.

For example:

### tsconfig.app.json

```
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    // ...
  },
  // ...
  "angularCompilerOptions": {
    "preserveWhitespaces": true,
    // ...
  }
}
```

For more information, see the [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html).

## [Template options](#template-options)

The following options are available for configuring the Angular AOT compiler.

### [`annotationsAs`](#annotationsas)

Modifies how Angular-specific annotations are emitted to improve tree-shaking. Non-Angular annotations are not affected. One of `static fields` or `decorators`. The default value is `static fields`.

* By default, the compiler replaces decorators with a static field in the class, which allows advanced tree-shakers like [Closure compiler](https://github.com/google/closure-compiler) to remove unused classes

* The `decorators` value leaves the decorators in place, which makes compilation faster. TypeScript emits calls to the `__decorate` helper. Use `--emitDecoratorMetadata` for runtime reflection.

  **HELPFUL:** That the resulting code cannot tree-shake properly.

### [`annotateForClosureCompiler`](#annotateforclosurecompiler)

When `true`, use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted JavaScript with [JSDoc](https://jsdoc.app) comments needed by the [Closure Compiler](https://github.com/google/closure-compiler). Default is `false`.

### [`compilationMode`](#compilationmode)

Specifies the compilation mode to use. The following modes are available:

| Modes       | Details                                                                                             |
| :---------- | :-------------------------------------------------------------------------------------------------- |
| `'full'`    | Generates fully AOT-compiled code according to the version of Angular that is currently being used. |
| `'partial'` | Generates code in a stable, but intermediate form suitable for a published library.                 |

The default value is `'full'`.

For most applications, `'full'` is the correct compilation mode.

Use `'partial'` for independently published libraries, such as npm packages. `'partial'` compilations output a stable, intermediate format which better supports usage by applications built at different Angular versions from the library. Libraries built at "HEAD" alongside their applications and using the same version of Angular such as in a mono-repository can use `'full'` since there is no risk of version skew.

### [`disableTypeScriptVersionCheck`](#disabletypescriptversioncheck)

When `true`, the compiler does not look at the TypeScript version and does not report an error when an unsupported version of TypeScript is used. Not recommended, as unsupported versions of TypeScript might have undefined behavior. Default is `false`.

### [`enableI18nLegacyMessageIdFormat`](#enablei18nlegacymessageidformat)

Instructs the Angular template compiler to create legacy ids for messages that are tagged in templates by the `i18n` attribute. See [Mark text for translations](guide/i18n/prepare#mark-text-in-component-template "Mark text in component template - Prepare component for translation | Angular") for more information about marking messages for localization.

Set this option to `false` unless your project relies upon translations that were created earlier using legacy IDs. Default is `true`.

The pre-Ivy message extraction tooling created a variety of legacy formats for extracted message IDs. These message formats have some issues, such as whitespace handling and reliance upon information inside the original HTML of a template.

The new message format is more resilient to whitespace changes, is the same across all translation file formats, and can be created directly from calls to [`$localize`](/api/localize/init/$localize). This allows [`$localize`](/api/localize/init/$localize) messages in application code to use the same ID as identical `i18n` messages in component templates.

**IMPORTANT:** This option is only supported by the `@angular-devkit/build-angular:browser` builder. When using the `@angular/build:application` builder (esbuild), this option has no effect and the new decimal message ID format is always used regardless of this setting.

### [`enableResourceInlining`](#enableresourceinlining)

When `true`, replaces the `templateUrl` and `styleUrls` properties in all [`@Component`](/api/core/Component) decorators with inline content in the `template` and `styles` properties.

When enabled, the `.js` output of `ngc` does not include any lazy-loaded template or style URLs.

For library projects created with the Angular CLI, the development configuration default is `true`.

### [`flatModuleId`](#flatmoduleid)

The module ID to use for importing a flat module (when `flatModuleOutFile` is `true`). References created by the template compiler use this module name when importing symbols from the flat module. Ignored if `flatModuleOutFile` is `false`.

### [`flatModuleOutFile`](#flatmoduleoutfile)

When `true`, generates a flat module index of the given filename and the corresponding flat module metadata. Use to create flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option is used, the `package.json` for the library should refer to the created flat module index instead of the library index file.

Produces only one `.metadata.json` file, which contains all the metadata necessary for symbols exported from the library index. In the created `.ngfactory.js` files, the flat module index is used to import symbols. Symbols that include both the public API from the library index and shrouded internal symbols.

By default, the `.ts` file supplied in the `files` field is assumed to be the library index. If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use. If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced.

A flat module index `.d.ts` and `.js` is created with the given `flatModuleOutFile` name in the same location as the library index `.d.ts` file.

For example, if a library uses the `public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field would be `["public_api.ts"]`. The `flatModuleOutFile` option could then be set, for example, to `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files. The `module` field of the library's `package.json` would be `"index.js"` and the `typings` field would be `"index.d.ts"`.

### [`generateCodeForLibraries`](#generatecodeforlibraries)

When `true`, creates factory files (`.ngfactory.js` and `.ngstyle.js`) for `.d.ts` files with a corresponding `.metadata.json` file. The default value is `true`.

When `false`, factory files are created only for `.ts` files. Do this when using factory summaries.

### [`preserveWhitespaces`](#preservewhitespaces)

When `false`, the default, removes blank text nodes from compiled templates, which results in smaller emitted template factory modules. Set to `true` to preserve blank text nodes.

**HELPFUL:** When using hydration, it is recommended that you use `preserveWhitespaces: false`, which is the default value. If you choose to enable preserving whitespaces by adding `preserveWhitespaces: true` to your tsconfig, it is possible you may encounter issues with hydration. This is not yet a fully supported configuration. Ensure this is also consistently set between the server and client tsconfig files. See the [hydration guide](guide/hydration#preserve-whitespaces-configuration) for more details.

### [`skipMetadataEmit`](#skipmetadataemit)

When `true`, does not produce `.metadata.json` files. Default is `false`.

The `.metadata.json` files contain information needed by the template compiler from a `.ts` file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information includes, for example, the content of annotations, such as a component's template, which TypeScript emits to the `.js` file but not to the `.d.ts` file.

You can set to `true` when using factory summaries, because the factory summaries include a copy of the information that is in the `.metadata.json` file.

Set to `true` if you are using TypeScript's `--outFile` option, because the metadata files are not valid for this style of TypeScript output. The Angular community does not recommend using `--outFile` with Angular. Use a bundler, such as [webpack](https://webpack.js.org), instead.

### [`skipTemplateCodegen`](#skiptemplatecodegen)

When `true`, does not emit `.ngfactory.js` and `.ngstyle.js` files. This turns off most of the template compiler and disables the reporting of template diagnostics.

Can be used to instruct the template compiler to produce `.metadata.json` files for distribution with an `npm` package. This avoids the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to `npm`.

For library projects created with the Angular CLI, the development configuration default is `true`.

### [`strictMetadataEmit`](#strictmetadataemit)

When `true`, reports an error to the `.metadata.json` file if `"skipMetadataEmit"` is `false`. Default is `false`. Use only when `"skipMetadataEmit"` is `false` and `"skipTemplateCodegen"` is `true`.

This option is intended to verify the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.

It is valid for `.metadata.json` files to contain errors. The template compiler reports these errors if the metadata is used to determine the contents of an annotation. The metadata collector cannot predict the symbols that are designed for use in an annotation. It preemptively includes error nodes in the metadata for the exported symbols. The template compiler can then use the error nodes to report an error if these symbols are used.

If the client of a library intends to use a symbol in an annotation, the template compiler does not normally report this. It gets reported after the client actually uses the symbol. This option allows detection of these errors during the build phase of the library and is used, for example, in producing Angular libraries themselves.

For library projects created with the Angular CLI, the development configuration default is `true`.

### [`strictInjectionParameters`](#strictinjectionparameters)

When `true`, reports an error for a supplied parameter whose injection type cannot be determined. When `false`, constructor parameters of classes marked with [`@Injectable`](/api/core/Injectable) whose type cannot be resolved produce a warning. The recommended value is `true`, but the default value is `false`.

When you use the Angular CLI command `ng new --strict`, it is set to `true` in the created project's configuration.

### [`strictTemplates`](#stricttemplates)

When `true`, enables [strict template type checking](tools/cli/template-typecheck#strict-mode).

The strictness flags that this option enables allow you to turn on and off specific types of strict template type checking. See [troubleshooting template errors](tools/cli/template-typecheck#troubleshooting-template-errors). Default is `true`.

### [`strictStandalone`](#strictstandalone)

When `true`, reports an error if a component, directive, or pipe is not standalone.

### [`trace`](#trace)

When `true`, prints extra information while compiling templates. Default is `false`.

### [`typeCheckHostBindings`](#typecheckhostbindings)

When `true`, enables type checking of expressions in the `host` object literal and [`@HostBinding`](/api/core/HostBinding)/[`@HostListener`](/api/core/HostListener) decorators of components and directives. Default is `true`.

## [Command line options](#command-line-options)

Most of the time, you interact with the Angular Compiler indirectly using [Angular CLI](reference/configs/angular-compiler-options). When debugging certain issues, you might find it useful to invoke the Angular Compiler directly. You can use the `ngc` command provided by the `@angular/compiler-cli` npm package to call the compiler from the command line.

The `ngc` command is a wrapper around TypeScript's `tsc` compiler command. The Angular Compiler is primarily configured through `tsconfig.json` while Angular CLI is primarily configured through `angular.json`.

Besides the configuration file, you can also use [`tsc` command line options](https://www.typescriptlang.org/docs/handbook/compiler-options.html) to configure `ngc`.

----
url: https://angular.dev/api/core/WritableSignal
----

```
    
      interface WritableSignal<T> extends Signal<T> {}
    
    
```

----
url: https://angular.dev/api/elements/NgElementStrategyFactory
----

```
    
      interface NgElementStrategyFactory {}
    
    
```

Creates a new instance to be used for an NgElement.

@paraminjector`Injector`

@returns`NgElementStrategy`

Jump to details

----
url: https://angular.dev/api/core/InjectAsyncOptions
----

```
    
      interface InjectAsyncOptions {}
    
    
```

A trigger to eagerly prefetch the lazy-loaded dependency before it is requested.

Jump to details

----
url: https://angular.dev/api/common/NgIf
----

Use the `@if` block instead. Intent to remove in a future major release

## [API](#api)

```
    
      class NgIf<T = unknown> {}
    
    
```

@param\_viewContainer`ViewContainerRef`

@paramtemplateRef`TemplateRef<NgIfContext<T>>`

@returns`NgIf<T>`

@deprecated

Use the `@if` block instead. Intent to remove in a future major release

The Boolean expression to evaluate as the condition for showing a template.

@deprecated

Use the `@if` block instead. Intent to remove in a future major release

A template to show if the condition expression evaluates to true.

@deprecated

Use the `@if` block instead. Intent to remove in a future major release

A template to show if the condition expression evaluates to false.

Assert the correct type of the expression bound to the `ngIf` input within the template.

The presence of this static field is a signal to the Ivy template type check compiler that when the [`NgIf`](/api/common/NgIf) structural directive renders its template, the type of the expression bound to `ngIf` should be narrowed in some way. For [`NgIf`](/api/common/NgIf), the binding expression itself is used to narrow its type, which allows the strictNullChecks feature of TypeScript to work with [`NgIf`](/api/common/NgIf).

Asserts the correct type of the context for the template that [`NgIf`](/api/common/NgIf) will render.

The presence of this method is a signal to the Ivy template type-check compiler that the [`NgIf`](/api/common/NgIf) structural directive renders its template with a specific context type.

@paramdir`NgIf<T>`

@paramctx`any`

@returns`ctx is NgIfContext<Exclude<T, false | 0 | '' | null | undefined>>`

## [Description](#description)

A structural directive that conditionally includes a template based on the value of an expression coerced to Boolean. When the expression evaluates to true, Angular renders the template provided in a `then` clause, and when false or null, Angular renders the template provided in an optional `else` clause. The default template for the `else` clause is blank.

A [shorthand form](guide/directives/structural-directives#structural-directive-shorthand) of the directive, `*ngIf="condition"`, is generally used, provided as an attribute of the anchor element for the inserted template. Angular expands this into a more explicit version, in which the anchor element is contained in an `<ng-template>` element.

Simple form with shorthand syntax:

```
<div *ngIf="condition">Content to render when condition is true.</div>
```

Simple form with expanded syntax:

```
<ng-template [ngIf]="condition"><div>Content to render when condition is
true.</div></ng-template>
```

Form with an "else" block:

```
<div *ngIf="condition; else elseBlock">Content to render when condition is true.</div>
<ng-template #elseBlock>Content to render when condition is false.</ng-template>
```

Shorthand form with "then" and "else" blocks:

```
<div *ngIf="condition; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>Content to render when condition is true.</ng-template>
<ng-template #elseBlock>Content to render when condition is false.</ng-template>
```

Form with storing the value locally:

```
<div *ngIf="condition as value; else elseBlock">{{value}}</div>
<ng-template #elseBlock>Content to render when value is null.</ng-template>
```

## [Exported by](#exported-by)

* `CommonModule`

## [Usage Notes](#usage-notes)

The `*ngIf` directive is most commonly used to conditionally show an inline template, as seen in the following example. The default `else` template is blank.

```
@Component({
  selector: 'ng-if-simple',
  imports: [NgIf],
  template: `
    <button (click)="show = !show">{{ show ? 'hide' : 'show' }}</button>
    show = {{ show }}
    <br />
    <div *ngIf="show">Text to show</div>
  `,
})
export class NgIfSimple {
  show = true;
}
```

### [Showing an alternative template using `else`](#showing-an-alternative-template-using-else)

To display a template when `expression` evaluates to false, use an `else` template binding as shown in the following example. The `else` binding points to an `<ng-template>` element labeled `#elseBlock`. The template can be defined anywhere in the component view, but is typically placed right after `ngIf` for readability.

```
@Component({
  selector: 'ng-if-else',
  imports: [NgIf],
  template: `
    <button (click)="show = !show">{{ show ? 'hide' : 'show' }}</button>
    show = {{ show }}
    <br />
    <div *ngIf="show; else elseBlock">Text to show</div>
    <ng-template #elseBlock>Alternate text while primary text is hidden</ng-template>
  `,
})
export class NgIfElse {
  show = true;
}
```

### [Using an external `then` template](#using-an-external-then-template)

In the previous example, the then-clause template is specified inline, as the content of the tag that contains the `ngIf` directive. You can also specify a template that is defined externally, by referencing a labeled `<ng-template>` element. When you do this, you can change which template to use at runtime, as shown in the following example.

```
@Component({
  selector: 'ng-if-then-else',
  imports: [NgIf],
  template: `
    <button (click)="show = !show">{{ show ? 'hide' : 'show' }}</button>
    <button (click)="switchPrimary()">Switch Primary</button>
    show = {{ show }}
    <br />
    <div *ngIf="show; then thenBlock; else elseBlock">this is ignored</div>
    <ng-template #primaryBlock>Primary text to show</ng-template>
    <ng-template #secondaryBlock>Secondary text to show</ng-template>
    <ng-template #elseBlock>Alternate text while primary text is hidden</ng-template>
  `,
})
export class NgIfThenElse implements OnInit {
  thenBlock: TemplateRef<any> | null = null;
  show = true;

  @ViewChild('primaryBlock', {static: true}) primaryBlock: TemplateRef<any> | null = null;
  @ViewChild('secondaryBlock', {static: true}) secondaryBlock: TemplateRef<any> | null = null;

  switchPrimary() {
    this.thenBlock = this.thenBlock === this.primaryBlock ? this.secondaryBlock : this.primaryBlock;
  }

  ngOnInit() {
    this.thenBlock = this.primaryBlock;
  }
}
```

### [Storing a conditional result in a variable](#storing-a-conditional-result-in-a-variable)

You might want to show a set of properties from the same object. If you are waiting for asynchronous data, the object can be undefined. In this case, you can use `ngIf` and store the result of the condition in a local variable as shown in the following example.

```
@Component({
  selector: 'ng-if-as',
  imports: [NgIf, JsonPipe, AsyncPipe],
  template: `
    <button (click)="nextUser()">Next User</button>
    <br />
    <div *ngIf="userObservable | async as user; else loading">
      Hello {{ user.last }}, {{ user.first }}!
    </div>
    <ng-template #loading let-user>Waiting... (user is {{ user | json }})</ng-template>
  `,
})
export class NgIfAs {
  userObservable = new Subject<{first: string; last: string}>();
  first = ['John', 'Mike', 'Mary', 'Bob'];
  firstIndex = 0;
  last = ['Smith', 'Novotny', 'Angular'];
  lastIndex = 0;

  nextUser() {
    let first = this.first[this.firstIndex++];
    if (this.firstIndex >= this.first.length) this.firstIndex = 0;
    let last = this.last[this.lastIndex++];
    if (this.lastIndex >= this.last.length) this.lastIndex = 0;
    this.userObservable.next({first, last});
  }
}
```

This code uses only one [`AsyncPipe`](/api/common/AsyncPipe), so only one subscription is created. The conditional statement stores the result of `userStream|async` in the local variable `user`. You can then bind the local `user` repeatedly.

The conditional displays the data only if `userStream` returns a value, so you don't need to use the safe-navigation-operator (`?.`) to guard against null values when accessing properties. You can display an alternative template while waiting for the data.

### [Shorthand syntax](#shorthand-syntax)

The shorthand syntax `*ngIf` expands into two separate template specifications for the "then" and "else" clauses. For example, consider the following shorthand statement, that is meant to show a loading page while waiting for data to be loaded.

```
<div class="hero-list" *ngIf="heroes else loading">
 ...
</div>

<ng-template #loading>
 <div>Loading...</div>
</ng-template>
```

You can see that the "else" clause references the `<ng-template>` with the `#loading` label, and the template for the "then" clause is provided as the content of the anchor element.

However, when Angular expands the shorthand syntax, it creates another `<ng-template>` tag, with `ngIf` and `ngIfElse` directives. The anchor element containing the template for the "then" clause becomes the content of this unlabeled `<ng-template>` tag.

```
<ng-template [ngIf]="heroes" [ngIfElse]="loading">
 <div class="hero-list">
  ...
 </div>
</ng-template>

<ng-template #loading>
 <div>Loading...</div>
</ng-template>
```

The presence of the implicit template object has implications for the nesting of structural directives. For more on this subject, see [Structural Directives](guide/directives/structural-directives#one-structural-directive-per-element).

Jump to details

----
url: https://angular.dev/api/router/CanActivate
----

```
    
      interface CanActivate {}
    
    
```

@paramroute`ActivatedRouteSnapshot`

@paramstate`RouterStateSnapshot`

@returns`MaybeAsync<GuardResult>`

## [Description](#description)

Interface that a class can implement to be a guard deciding if a route can be activated. If all guards return `true`, navigation continues. If any guard returns `false`, navigation is cancelled. If any guard returns a [`UrlTree`](/api/router/UrlTree), the current navigation is cancelled and a new navigation begins to the [`UrlTree`](/api/router/UrlTree) returned from the guard.

The following example implements a [`CanActivate`](/api/router/CanActivate) function that checks whether the current user has permission to activate the requested route.

```
class UserToken {}
class Permissions {
  canActivate(): boolean {
    return true;
  }
}

@Injectable()
class CanActivateTeam implements CanActivate {
  private readonly permissions = inject(Permissions);
  private readonly currentUser = inject(UserToken);

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): MaybeAsync<GuardResult> {
    return this.permissions.canActivate(this.currentUser, route.params.id);
  }
}
```

Here, the defined guard function is provided as part of the [`Route`](/api/router/Route) object in the router configuration:

```
@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'team/:id',
        component: TeamComponent,
        canActivate: [CanActivateTeam]
      }
    ])
  ],
  providers: [CanActivateTeam, UserToken, Permissions]
})
class AppModule {}
```

Jump to details

----
url: https://angular.dev/api/platform-browser/withNoHttpTransferCache
----

## [API](/api/platform-browser/withNoHttpTransferCache#api)

```
function withNoHttpTransferCache(): HydrationFeature<HydrationFeatureKind.NoHttpTransferCache>;
```

@returns`HydrationFeature<HydrationFeatureKind.NoHttpTransferCache>`

Jump to details

----
url: https://angular.dev/errors/NG01354
----

This warning is emitted when [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) (or any template-driven form control) is placed inside a child component whose parent component hosts a form directive ([`NgForm`](/api/forms/NgForm) or [`FormGroupDirective`](/api/forms/FormGroupDirective)), but the control cannot register with that form.

The root cause is that [`NgModel`](/api/forms/NgModel) injects [`ControlContainer`](/api/forms/ControlContainer) with [`@Host()`](/api/core/Host), which stops the injector from crossing component boundaries. The form directive in the parent component's template is invisible to [`NgModel`](/api/forms/NgModel) in the child's template.

As a result, the child's controls silently act as standalone — they do not participate in the parent form's value, validity, or submission state.

## [Fixing the warning](#fixing-the-warning)

### [Option 1: Bridge the `ControlContainer` with `viewProviders`](#option-1-bridge-the-controlcontainer-with-viewproviders)

Add `viewProviders` to the child component so it re-exports the parent form into its own view. Use the same class that the warning names — [`NgForm`](/api/forms/NgForm) for template-driven forms (`<form>`), or [`FormGroupDirective`](/api/forms/FormGroupDirective) for reactive forms (`[formGroup]`):

```
// Parent uses a template-driven form: <form> ... <app-child> </form>
import {ControlContainer, NgForm} from '@angular/forms';

@Component({
  selector: 'app-child',
  template: `<input name="email" [(ngModel)]="email" />`,
  viewProviders: [{provide: ControlContainer, useExisting: NgForm}],
})
export class Child {
  email = '';
}
```

```
// Parent uses a reactive form: <div [formGroup]="myGroup"> ... <app-child> </div>
import {ControlContainer, FormGroupDirective} from '@angular/forms';

@Component({
  selector: 'app-child',
  template: `<input name="email" [(ngModel)]="email" />`,
  viewProviders: [{provide: ControlContainer, useExisting: FormGroupDirective}],
})
export class Child {
  email = '';
}
```

The parent form then sees and validates the child's controls normally.

### [Option 2: Mark the control as standalone](#option-2-mark-the-control-as-standalone)

If the child's controls should genuinely be independent of the parent form, opt out explicitly:

```
<input name="email" [(ngModel)]="email" [ngModelOptions]="{standalone: true}" />
```

## [Debugging the warning](#debugging-the-warning)

The warning message includes the error code `NG01354`. Use the call stack to locate which [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) triggered it. Check whether the component that owns the `<form>` tag is the same component that owns the [`ngModel`](/api/forms/SelectMultipleControlValueAccessor) input — if they differ, apply one of the options above.

----
url: https://angular.dev/api/core/PlatformRef
----

```
    
      class PlatformRef {}
    
    
```

@param\_injector`Injector`

@returns`PlatformRef`

Creates an instance of an [`@NgModule`](/api/core/NgModule) for the given platform.

@deprecated

Passing NgModule factories as the [`PlatformRef.bootstrapModuleFactory`](/api/core/PlatformRef#bootstrapModuleFactory) function argument is deprecated. Use the [`PlatformRef.bootstrapModule`](/api/core/PlatformRef#bootstrapModule) API instead.

@parammoduleFactory`NgModuleFactory<M>`

@paramoptions`(BootstrapOptions & { applicationProviders?: (Provider | EnvironmentProviders)[] | undefined; }) | undefined`

@returns`Promise<NgModuleRef<M>>`

Creates an instance of an [`@NgModule`](/api/core/NgModule) for a given platform.

@parammoduleType`Type<M>`

@paramcompilerOptions`(CompilerOptions & BootstrapOptions & { applicationProviders?: (Provider | EnvironmentProviders)[] | undefined; }) | (CompilerOptions & BootstrapOptions & { applicationProviders?: (Provider | EnvironmentProviders)[] | undefined; })[]`

@returns`Promise<NgModuleRef<M>>`

Usage notes

### [Simple Example](#simple-example)

```
@NgModule({
  imports: [BrowserModule]
})
class MyModule {}

let moduleRef = platformBrowser().bootstrapModule(MyModule);
```

Registers a listener to be called when the platform is destroyed.

@paramcallback`() => void`

@returns`void`

Retrieves the platform [`Injector`](/api/core/Injector), which is the parent injector for every Angular application on the page and provides singleton providers.

Destroys the current Angular platform and all Angular applications on the page. Destroys all modules and listeners registered with the platform.

@returns`void`

Indicates whether this instance was destroyed.

Jump to details

----
url: https://angular.dev/api/core/makeStateKey
----

```
function makeStateKey<T = void>(key: string): StateKey<T>;
```

@paramkey`string`

@returns`StateKey<T>`

## [Description](#description)

Create a `StateKey<T>` that can be used to store value of type T with [`TransferState`](/api/core/TransferState).

Example:

```
const COUNTER_KEY = makeStateKey<number>('counter');
let value = 10;

transferState.set(COUNTER_KEY, value);
```

Jump to details

----
url: https://angular.dev/api/core/ng-content
----

The `<ng-content>` element specifies where to project content inside a component template.

## [Attributes](#attributes)

| Attribute | Description                                                             |
| --------- | ----------------------------------------------------------------------- |
| `select`  | CSS selector. Matching elements are projected into this `<ng-content>`. |

Only select elements from the projected content that match the given CSS `selector`.

Angular supports [selectors](https://developer.mozilla.org/docs/Web/CSS/CSS_Selectors) for any combination of tag name, attribute, CSS class, and the `:not` pseudo-class.

Jump to details

----
url: https://angular.dev/guide/components/importing
----

[edit](https://github.com/angular/angular/edit/main/adev/src/content/guide/components/anatomy-of-components.md "Edit this page")

**TIP:** This guide assumes you've already read the [Essentials Guide](essentials). Read that first if you're new to Angular.

Every component must have:

* A TypeScript class with *behaviors* such as handling user input and fetching data from a server
* An HTML template that controls what renders into the DOM
* A [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors) that defines how the component is used in HTML

You provide Angular-specific information for a component by adding a [`@Component`](/api/core/Component) [decorator](https://www.typescriptlang.org/docs/handbook/decorators.html) on top of the TypeScript class:

```
@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
})
export class ProfilePhoto {}
```

For full details on writing Angular templates, including data binding, event handling, and control flow, see the [Templates guide](guide/templates).

The object passed to the [`@Component`](/api/core/Component) decorator is called the component's **metadata**. This includes the `selector`, `template`, and other properties described throughout this guide.

Components can optionally include a list of CSS styles that apply to that component's DOM:

```
@Component({
  selector: 'profile-photo',
  template: `<img src="profile-photo.jpg" alt="Your profile photo" />`,
  styles: `
    img {
      border-radius: 50%;
    }
  `,
})
export class ProfilePhoto {}
```

By default, a component's styles only affect elements defined in that component's template. See [Styling Components](guide/components/styling) for details on Angular's approach to styling.

You can alternatively choose to write your template and styles in separate files:

```
@Component({
  selector: 'profile-photo',
  templateUrl: 'profile-photo.html',
  styleUrl: 'profile-photo.css',
})
export class ProfilePhoto {}
```

This can help separate the concerns of *presentation* from *behavior* in your project. You can choose one approach for your entire project, or you can decide which to use for each component.

Both `templateUrl` and `styleUrl` are relative to the directory in which the component resides.

## [Using components](/guide/components#using-components)

### [Imports in the `@Component` decorator](/guide/components#imports-in-the-component-decorator)

To use a component, [directive](guide/directives), or [pipe](guide/templates/pipes), you must add it to the `imports` array in the [`@Component`](/api/core/Component) decorator:

```
import {ProfilePhoto} from './profile-photo';

@Component({
  // Import the `ProfilePhoto` component in
  // order to use it in this component's template.
  imports: [ProfilePhoto],
  /* ... */
})
export class UserProfile {}
```

By default, Angular components are *standalone*, meaning that you can directly add them to the `imports` array of other components. Components created with an earlier version of Angular may instead specify `standalone: false` in their [`@Component`](/api/core/Component) decorator. For these components, you instead import the [`NgModule`](/api/core/NgModule) in which the component is defined. See the full [`NgModule` guide](guide/ngmodules/overview) for details.

**IMPORTANT:** In Angular versions before 19.0.0, the `standalone` option defaults to `false`.

### [Showing components in a template](/guide/components#showing-components-in-a-template)

Every component defines a [CSS selector](https://developer.mozilla.org/docs/Learn/CSS/Building_blocks/Selectors):

```
@Component({
  selector: 'profile-photo',
  ...
})
export class ProfilePhoto { }
```

See [Component Selectors](guide/components/selectors) for details about which types of selectors Angular supports and guidance on choosing a selector.

You show a component by creating a matching HTML element in the template of *other* components:

```
@Component({
  selector: 'profile-photo',
})
export class ProfilePhoto {}

@Component({
  imports: [ProfilePhoto],
  template: `<profile-photo />`,
})
export class UserProfile {}
```

Angular creates an instance of the component for every matching HTML element it encounters. The DOM element that matches a component's selector is referred to as that component's **host element**. The contents of a component's template are rendered inside its host element.

The DOM rendered by a component, corresponding to that component's template, is called that component's **view**.

In composing components in this way, **you can think of your Angular application as a tree of components**.

This tree structure is important to understanding several other Angular concepts, including [dependency injection](guide/di) and [child queries](guide/components/queries).

----
url: https://angular.dev/cli/generate/interceptor
----

```
ng generate interceptor [name] [options]
```

Creates a new interceptor in your project. Interceptors are used to intercept and modify HTTP requests and responses before they reach their destination. This allows you to perform tasks like adding authentication headers, handling errors, or logging requests. This schematic generates the necessary files and boilerplate code for a new interceptor.

## [Arguments](#arguments)

`name`

The name for the new interceptor. This will be used to create the interceptor's class and spec files (e.g., `my-interceptor.interceptor.ts` and `my-interceptor.interceptor.spec.ts`).

Value Type`string`

## [Options](#options)

`flat`

Creates the new interceptor files at the top level of the current project. If set to false, a new folder with the interceptor's name will be created to contain the files.

Value Type`boolean`Default`true`

`functional`

Creates the interceptor as a function `HttpInterceptorFn` instead of a class. Functional interceptors can be simpler for basic scenarios.

Value Type`boolean`Default`true`

`project`

The name of the project where the interceptor should be created. If not specified, the CLI will determine the project from the current directory.

Value Type`string`

`skip-tests`

Skip the generation of a unit test file `spec.ts` for the new interceptor.

Value Type`boolean`Default`false`

`type-separator`

The separator character to use before the type within the generated file's name. For example, if you set the option to `.`, the file will be named `example.interceptor.ts`.

Value Type`string`Allowed Values`-`, `.`Default`-`

Jump to details

----
url: https://angular.dev/guide/components/content-projection
----

You often need to create components that act as containers for different types of content. For example, you may want to create a custom card component:

```
@Component({
  selector: 'custom-card',
  template: '<div class="card-shadow"> <!-- card content goes here --> </div>',
})
export class CustomCard {
  /* ... */
}
```

**You can use the `<ng-content>` element as a placeholder to mark where content should go**:

```
@Component({
  selector: 'custom-card',
  template: '<div class="card-shadow"> <ng-content/> </div>',
})
export class CustomCard {
  /* ... */
}
```

**TIP:** `<ng-content>` works similarly to [the native `<slot>` element](https://developer.mozilla.org/docs/Web/HTML/Element/slot), but with some Angular-specific functionality.

When you use a component with `<ng-content>`, any children of the component host element are rendered, or **projected**, at the location of that `<ng-content>`:

```
// Component source
@Component({
  selector: 'custom-card',
  template: `
    <div class="card-shadow">
      <ng-content />
    </div>
  `,
})
export class CustomCard {
  /* ... */
}
```

```
<!-- Using the component -->
<custom-card>
  <p>This is the projected content</p>
</custom-card>
```

```
<!-- The rendered DOM -->
<custom-card>
  <div class="card-shadow">
    <p>This is the projected content</p>
  </div>
</custom-card>
```

Angular refers to any children of a component passed this way as that component's **content**. This is distinct from the component's **view**, which refers to the elements defined in the component's template.

**The `<ng-content>` element is neither a component nor DOM element**. Instead, it is a special placeholder that tells Angular where to render content. Angular's compiler processes all `<ng-content>` elements at build-time. You cannot insert, remove, or modify `<ng-content>` at run time. You cannot add directives, styles, or arbitrary attributes to `<ng-content>`.

**IMPORTANT:** You should not conditionally include `<ng-content>` with `@if`, `@for`, or `@switch`. Angular always instantiates and creates DOM nodes for content rendered to a `<ng-content>` placeholder, even if that `<ng-content>` placeholder is hidden. For conditional rendering of component content, see [Template fragments](api/core/ng-template).

## [Multiple content placeholders](#multiple-content-placeholders)

Angular supports projecting multiple different elements into different `<ng-content>` placeholders based on CSS selector. Expanding the card example from above, you could create two placeholders for a card title and a card body by using the `select` attribute:

```
@Component({
  selector: 'card-title',
  template: `<ng-content>card-title</ng-content>`,
})
export class CardTitle {}

@Component({
  selector: 'card-body',
  template: `<ng-content>card-body</ng-content>`,
})
export class CardBody {}
```

```
<!-- Component template -->
@Component({
  selector: 'custom-card',
  template: `
  <div class="card-shadow">
    <ng-content select="card-title" />
    <div class="card-divider"></div>
    <ng-content select="card-body" />
  </div>
  `,
})
export class CustomCard {}
```

```
<!-- Using the component -->
@Component({
  selector: 'app-root',
  imports: [CustomCard, CardTitle, CardBody],
  template: `
    <custom-card>
      <card-title>Hello</card-title>
      <card-body>Welcome to the example</card-body>
    </custom-card>
`,
})
export class App {}
```

```
<!-- Rendered DOM -->
<custom-card>
  <div class="card-shadow">
    <card-title>Hello</card-title>
    <div class="card-divider"></div>
    <card-body>Welcome to the example</card-body>
  </div>
</custom-card>
```

The `<ng-content>` placeholder supports the same CSS selectors as [component selectors](guide/components/selectors).

If you include one or more `<ng-content>` placeholders with a `select` attribute and one `<ng-content>` placeholder without a `select` attribute, the latter captures all elements that did not match a `select` attribute:

```
<!-- Component template -->
<div class="card-shadow">
  <ng-content select="card-title" />
  <div class="card-divider"></div>
  <!-- capture anything except "card-title" -->
  <ng-content />
</div>
```

```
<!-- Using the component -->
<custom-card>
  <card-title>Hello</card-title>
  <img src="..." />
  <p>Welcome to the example</p>
</custom-card>
```

```
<!-- Rendered DOM -->
<custom-card>
  <div class="card-shadow">
    <card-title>Hello</card-title>
    <div class="card-divider"></div>
    <img src="..." />
    <p>Welcome to the example</p>
  </div>
</custom-card>
```

If a component does not include an `<ng-content>` placeholder without a `select` attribute, any elements that don't match one of the component's placeholders do not render into the DOM.

## [Fallback content](#fallback-content)

Angular can show *fallback content* for a component's `<ng-content>` placeholder if that component doesn't have any matching child content. You can specify fallback content by adding child content to the `<ng-content>` element itself.

```
<!-- Component template -->
<div class="card-shadow">
  <ng-content select="card-title">Default Title</ng-content>
  <div class="card-divider"></div>
  <ng-content select="card-body">Default Body</ng-content>
</div>
```

```
<!-- Using the component -->
<custom-card>
  <card-title>Hello</card-title>
  <!-- No card-body provided -->
</custom-card>
```

```
<!-- Rendered DOM -->
<custom-card>
  <div class="card-shadow">
    <card-title>Hello</card-title>
    <div class="card-divider"></div>
    Default Body
  </div>
</custom-card>
```

## [Aliasing content for projection](#aliasing-content-for-projection)

Angular supports a special attribute, `ngProjectAs`, that allows you to specify a CSS selector on any element. Whenever an element with `ngProjectAs` is checked against an `<ng-content>` placeholder, Angular compares against the `ngProjectAs` value instead of the element's identity:

```
<!-- Component template -->
<div class="card-shadow">
  <ng-content select="card-title" />
  <div class="card-divider"></div>
  <ng-content />
</div>
```

```
<!-- Using the component -->
<custom-card>
  <h3 ngProjectAs="card-title">Hello</h3>

  <p>Welcome to the example</p>
</custom-card>
```

```
<!-- Rendered DOM -->
<custom-card>
  <div class="card-shadow">
    <h3>Hello</h3>
    <div class="card-divider"></div>
    <p>Welcome to the example</p>
  </div>
</custom-card>
```

`ngProjectAs` supports only static values and cannot be bound to dynamic expressions.

## [Caveats](#caveats)

### [Projected content lives in the parent's view](#projected-content-lives-in-the-parents-view)

Even though projected content is *rendered* inside the receiving component, it is still owned by the component that declared it. Angular tracks it as part of the parent's view, which has a couple of side effects worth knowing about.

**Change detection:** Projected content is checked when the *parent* runs change detection. If the receiving component uses `OnPush`, Angular can skip checking that component's own template — but it won't skip the projected content, because that belongs to the parent.

```
<!-- Parent template (default change detection) -->
<onpush-wrapper>
  <!-- Still checked on every parent cycle, OnPush doesn't help here -->
  <expensive-component />
</onpush-wrapper>
```

**Dependency injection:** Projected content gets its dependencies from the parent's injector, not from the receiving component's `viewProviders`. See [Providers and viewProviders](guide/di/hierarchical-dependency-injection) for details.

### [Some library components don't support projected children](#some-library-components-dont-support-projected-children)

Certain components — menus, tabs, lists — use [`ContentChildren`](/api/core/ContentChildren) to find their children and wire up behavior like keyboard navigation, focus management, or ARIA attributes. They're written assuming they own their children directly, so projecting external content into them tends to break things in subtle ways.

For example, wrapping `<mat-menu-item>` elements in an extra layer and projecting them into `<mat-menu>` can silently break keyboard navigation and screen reader support. The query still finds the items, but the internal setup that makes them interactive may not work correctly when the items come from a different view context.

If a library component manages its children's behavior, check its docs before reaching for content projection — it may not be supported.

----
url: https://angular.dev/api/common/upgrade/$locationShim
----

```
    
      class $locationShim {
}
    
    
```

@param$injector`any`

@paramlocation`Location`

@paramplatformLocation`PlatformLocation`

@paramurlCodec`UrlCodec`

@paramlocationStrategy`LocationStrategy`

@returns`$locationShim`

Registers listeners for URL changes. This API is used to catch updates performed by the AngularJS framework. These changes are a subset of the `$locationChangeStart` and `$locationChangeSuccess` events which fire when AngularJS updates its internally-referenced version of the browser URL.

It's possible for `$locationChange` events to happen, but for the browser URL (window\.location) to remain unchanged. This `onChange` callback will fire only when AngularJS actually updates the browser URL (window\.location).

@paramfn`(url: string, state: unknown, oldUrl: string, oldState: unknown) => void`

The callback function that is triggered for the listener when the URL changes.

@paramerr`(e: Error) => void`

The callback function that is triggered when an error occurs.

@returns`void`

Parses the provided URL, and sets the current URL to the parsed result.

@paramurl`string`

The URL string.

@returns`void`

Parses the provided URL and its relative URL.

@paramurl`string`

The full URL string.

@paramrelHref`string | null | undefined`

A URL string relative to the full URL string.

@returns`boolean`

Retrieves the full URL representation with all segments encoded according to rules specified in [RFC 3986](https://tools.ietf.org/html/rfc3986).

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let absUrl = $location.absUrl();
// => "http://example.com/#/some/path?foo=bar&baz=xoxo"
```

@returns`string`

Retrieves the current URL, or sets a new URL. When setting a URL, changes the path, search, and hash, and returns a reference to its own instance.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let url = $location.url();
// => "/some/path?foo=bar&baz=xoxo"
```

@returns`string`

@paramurl`string`

@returns`this`

Retrieves the protocol of the current URL.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let protocol = $location.protocol();
// => "http"
```

@returns`string`

Retrieves the protocol of the current URL.

In contrast to the non-AngularJS version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let host = $location.host();
// => "example.com"

// given URL http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
host = $location.host();
// => "example.com"
host = location.host;
// => "example.com:8080"
```

@returns`string`

Retrieves the port of the current URL.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let port = $location.port();
// => 80
```

@returns`number | null`

Retrieves the path of the current URL, or changes the path and returns a reference to its own instance.

Paths should always begin with forward slash (/). This method adds the forward slash if it is missing.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let path = $location.path();
// => "/some/path"
```

@returns`string`

@parampath`string | number | null`

@returns`this`

Retrieves a map of the search parameters of the current URL, or changes a search part and returns a reference to its own instance.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo
let searchObject = $location.search();
// => {foo: 'bar', baz: 'xoxo'}

// set foo to 'yipee'
$location.search('foo', 'yipee');
// $location.search() => {foo: 'yipee', baz: 'xoxo'}
```

@returns`{ [key: string]: unknown; }`

@paramsearch`string | number | { [key: string]: unknown; }`

@returns`this`

@paramsearch`string | number | { [key: string]: unknown; }`

@paramparamValue`string | number | boolean | string[] | null | undefined`

@returns`this`

Retrieves the current hash fragment, or changes the hash fragment and returns a reference to its own instance.

```
// given URL http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue
let hash = $location.hash();
// => "hashValue"
```

@returns`string`

@paramhash`string | number | null`

@returns`this`

Changes to `$location` during the current `$digest` will replace the current history record, instead of adding a new one.

@returns`this`

Retrieves the history state object when called without any parameter.

Change the history state object when called with one parameter and return `$location`. The state object is later passed to `pushState` or `replaceState`.

This method is supported only in HTML5 mode and only in browsers supporting the HTML5 History API methods such as `pushState` and `replaceState`. If you need to support older browsers (like Android < 4.0), don't use this method.

@returns`unknown`

@paramstate`unknown`

@returns`this`

Jump to details

----
url: https://angular.dev/api/core/globals/getOwningComponent
----

```
function getOwningComponent<T>(elementOrDir: {} | Element): T | null;
```

@paramelementOrDir`{} | Element`

DOM element, component or directive instance for which to retrieve the root components.

@returns`T | null`

Component instance whose view owns the DOM element or null if the element is not part of a component view.

## [Description](#description)

Retrieves the component instance whose view contains the DOM element.

For example, if `<child-comp>` is used in the template of `<app-comp>` (i.e. a `ViewChild` of `<app-comp>`), calling [`getOwningComponent`](/api/core/globals/getOwningComponent) on `<child-comp>` would return `<app-comp>`.

Jump to details

----
url: https://angular.dev/api/elements/NgElementStrategyEvent
----

```
    
      interface NgElementStrategyEvent {}
    
    
```

Jump to details

----
url: https://angular.dev/guide/signals/linked-signal
----

You can use the [`signal`](/api/core/signal) function to hold some state in your Angular code. Sometimes, this state depends on some *other* state. For example, imagine a component that lets the user select a shipping method for an order:

```
@Component({
  /* ... */
})
export class ShippingMethodPicker {
  shippingOptions: Signal<ShippingMethod[]> = getShippingOptions();

  // Select the first shipping option by default.
  selectedOption = signal(this.shippingOptions()[0]);

  changeShipping(newOptionIndex: number) {
    this.selectedOption.set(this.shippingOptions()[newOptionIndex]);
  }
}
```

In this example, the `selectedOption` defaults to the first option, but changes if the user selects another option. But `shippingOptions` is a signal— its value may change! If `shippingOptions` changes, `selectedOption` may contain a value that is no longer a valid option.

**The [`linkedSignal`](/api/core/linkedSignal) function lets you create a signal to hold some state that is intrinsically *linked* to some other state.** Revisiting the example above, [`linkedSignal`](/api/core/linkedSignal) can replace [`signal`](/api/core/signal):

```
@Component({
  /* ... */
})
export class ShippingMethodPicker {
  shippingOptions: Signal<ShippingMethod[]> = getShippingOptions();

  // Initialize selectedOption to the first shipping option.
  selectedOption = linkedSignal(() => this.shippingOptions()[0]);

  changeShipping(index: number) {
    this.selectedOption.set(this.shippingOptions()[index]);
  }
}
```

[`linkedSignal`](/api/core/linkedSignal) works similarly to [`signal`](/api/core/signal) with one key difference— instead of passing a default value, you pass a *computation function*, just like [`computed`](/api/core/computed). When the value of the computation changes, the value of the [`linkedSignal`](/api/core/linkedSignal) changes to the computation result. This helps ensure that the [`linkedSignal`](/api/core/linkedSignal) always has a valid value.

The following example shows how the value of a [`linkedSignal`](/api/core/linkedSignal) can change based on its linked state:

```
const shippingOptions = signal(['Ground', 'Air', 'Sea']);
const selectedOption = linkedSignal(() => shippingOptions()[0]);
console.log(selectedOption()); // 'Ground'

selectedOption.set(shippingOptions()[2]);
console.log(selectedOption()); // 'Sea'

shippingOptions.set(['Email', 'Will Call', 'Postal service']);
console.log(selectedOption()); // 'Email'
```

## [Accounting for previous state](#accounting-for-previous-state)

In some cases, the computation for a [`linkedSignal`](/api/core/linkedSignal) needs to account for the previous value of the [`linkedSignal`](/api/core/linkedSignal).

In the example above, `selectedOption` always updates back to the first option when `shippingOptions` changes. You may, however, want to preserve the user's selection if their selected option is still somewhere in the list. To accomplish this, you can create a [`linkedSignal`](/api/core/linkedSignal) with a separate *source* and *computation*:

```
interface ShippingMethod {
  id: number;
  name: string;
}

@Component({
  /* ... */
})
export class ShippingMethodPicker {
  constructor() {
    this.changeShipping(2);
    this.changeShippingOptions();
    console.log(this.selectedOption()); // {"id":2,"name":"Postal Service"}
  }

  shippingOptions = signal<ShippingMethod[]>([
    {id: 0, name: 'Ground'},
    {id: 1, name: 'Air'},
    {id: 2, name: 'Sea'},
  ]);

  selectedOption = linkedSignal<ShippingMethod[], ShippingMethod>({
    // `selectedOption` is set to the `computation` result whenever this `source` changes.
    source: this.shippingOptions,
    computation: (newOptions, previous) => {
      // If the newOptions contain the previously selected option, preserve that selection.
      // Otherwise, default to the first option.
      return newOptions.find((opt) => opt.id === previous?.value.id) ?? newOptions[0];
    },
  });

  changeShipping(index: number) {
    this.selectedOption.set(this.shippingOptions()[index]);
  }

  changeShippingOptions() {
    this.shippingOptions.set([
      {id: 0, name: 'Email'},
      {id: 1, name: 'Sea'},
      {id: 2, name: 'Postal Service'},
    ]);
  }
}
```

When you create a [`linkedSignal`](/api/core/linkedSignal), you can pass an object with separate `source` and `computation` properties instead of providing just a computation.

The `source` can be any signal, such as a [`computed`](/api/core/computed) or component `input`. The [`linkedSignal`](/api/core/linkedSignal) updates its value when the `source` changes or when any signal referenced in the `computation` changes, updating its value with the result of the provided `computation`.

The `computation` is a function that receives the new value of `source` and a `previous` object. The `previous` object has two properties— `previous.source` is the previous value of `source`, and `previous.value` is the previous value of the [`linkedSignal`](/api/core/linkedSignal). You can use these previous values to decide the new result of the computation.

**HELPFUL:** When using the `previous` parameter, it is necessary to provide the generic type arguments of [`linkedSignal`](/api/core/linkedSignal) explicitly. The first generic type corresponds with the type of `source` and the second generic type determines the output type of `computation`.

## [Custom equality comparison](#custom-equality-comparison)

[`linkedSignal`](/api/core/linkedSignal), as any other signal, can be configured with a custom equality function. This function is used by downstream dependencies to determine if that value of the [`linkedSignal`](/api/core/linkedSignal) (result of a computation) changed:

```
const activeUser = signal({id: 123, name: 'Morgan', isAdmin: true});

const activeUserEditCopy = linkedSignal(() => activeUser(), {
  // Consider the user as the same if it's the same `id`.
  equal: (a, b) => a.id === b.id,
});

// Or, if separating `source` and `computation`
const activeUserEditCopy = linkedSignal({
  source: activeUser,
  computation: (user) => user,
  equal: (a, b) => a.id === b.id,
});
```

## [Customizing the set operation](#customizing-the-set-operation)

Sometimes you may want the `set` and `update` operations of a [`linkedSignal`](/api/core/linkedSignal) to write back to the source of truth instead of updating the [`linkedSignal`](/api/core/linkedSignal)'s value directly. You can customize this behavior by passing a `set` function in the options.

The custom `set` function receives two arguments:

1. The new value being set.
2. A `rawSet` function, which you can invoke to update the [`linkedSignal`](/api/core/linkedSignal)'s internal state directly (matching the default behavior).

**NOTE:** Using `rawSet` allows you to update the [`linkedSignal`](/api/core/linkedSignal)'s value directly. This can be useful to prevent the computation from running, for example if it is an expensive derivation and you already know the result.

### [Writing back to a source signal](#writing-back-to-a-source-signal)

Consider a component that displays and allows editing temperature in Fahrenheit, but uses a Celsius signal as its source of truth:

```
const tempC = signal(0);
const tempF = linkedSignal(() => (tempC() * 9) / 5 + 32, {
  set: (valF) => tempC.set(((valF - 32) * 5) / 9),
});

console.log(tempF()); // 32

// Setting Fahrenheit updates Celsius, which reactively updates Fahrenheit
tempF.set(212);
console.log(tempC()); // 100
console.log(tempF()); // 212
```

### [Updating a property inside a parent object](#updating-a-property-inside-a-parent-object)

Another common scenario is updating a specific property inside a parent object. The parent is held in a signal, and you link to a nested property:

```
interface Order {
  id: number;
  shippingMethod: string;
}

const order = signal<Order>({
  id: 42,
  shippingMethod: 'Ground',
});

const shippingMethod = linkedSignal(() => order().shippingMethod, {
  set: (newMethod) => {
    // Perform an immutable update to write the change back to the order
    order.update((currentOrder) => ({
      ...currentOrder,
      shippingMethod: newMethod,
    }));
  },
});

console.log(shippingMethod()); // 'Ground'

// Updating the shippingMethod updates the parent order object
shippingMethod.set('Air');
console.log(order()); // { id: 42, shippingMethod: 'Air' }
console.log(shippingMethod()); // 'Air'
```

----
url: https://angular.dev/api/cdk/testing/TextOptions
----

```
    
      interface TextOptions {}
    
    
```

Optional selector for elements whose content should be excluded from the text string.

Jump to details

----
url: https://angular.dev/best-practices/runtime-performance
----

Fast rendering is critical for Angular and we've built the framework with a lot of optimizations in mind to help you develop performant apps. To better understand the performance of your app we offer [Angular DevTools](tools/devtools) and a [video guide](https://www.youtube.com/watch?v=FjyX_hkscII) on how to use Chrome DevTools for profiling. In this section we cover the most common performance optimization techniques.

**Change detection** is the process through which Angular checks to see whether your application state has changed, and if any DOM needs to be updated. At a high level, Angular walks your components from top to bottom, looking for changes. Angular runs its change detection mechanism periodically so that changes to the data model are reflected in an application’s view. Change detection can be triggered either manually or through an asynchronous event (for example, a user interaction or an XMLHttpRequest completion).

Change detection is highly optimized and performant, but it can still cause slowdowns if the application runs it too frequently.

In this guide, you’ll learn how to control and optimize the change detection mechanism by skipping parts of your application and running change detection only when necessary.

Watch this video if you prefer to learn more about performance optimizations in a media format:

[](https://www.youtube.com/watch?v=f8sA-i6gkGQ\&autoplay=1)

----
url: https://angular.dev/errors/NG2009
----

The selector of a component using [`ViewEncapsulation.ShadowDom`](/api/core/ViewEncapsulation#ShadowDom) doesn't match the custom element tag name requirements.

In order for a tag name to be considered a valid custom element name, it has to:

* Be in lower case.
* Contain a hyphen.
* Start with a letter (a-z).

## [Debugging the error](#debugging-the-error)

Rename your component's selector so that it matches the requirements.

**Before:**

```
@Component({
  selector: 'comp',
  encapsulation: ViewEncapsulation.ShadowDom
…
})
```

**After:**

```
@Component({
  selector: 'app-comp',
  encapsulation: ViewEncapsulation.ShadowDom
…
})
```

----
url: https://angular.dev/tools/cli/template-typecheck
----

## [Overview of template type checking](#overview-of-template-type-checking)

Just as TypeScript catches type errors in your code, Angular checks the expressions and bindings within the templates of your application and can report any type errors it finds. Angular has two modes of doing this, depending on the value of the `strictTemplates` flag in [Angular's compiler options](reference/configs/angular-compiler-options).

### [Basic mode](#basic-mode)

In the basic type-checking mode, with the `strictTemplates` flag set to `false`, Angular validates only top-level expressions in a template.

If you write `<map [city]="user.address.city">`, the compiler verifies the following:

* `user` is a property on the component class
* `user` is an object with an address property
* `user.address` is an object with a city property

The compiler does not verify that the value of `user.address.city` is assignable to the city input of the `<map>` component.

The compiler also has some major limitations in this mode:

* Importantly, it doesn't check embedded views, such as `*ngIf`, `*ngFor`, other `<ng-template>` embedded view.
* It doesn't figure out the types of `#refs`, the results of pipes, or the type of `$event` in event bindings.

In many cases, these things end up as type `any`, which can cause subsequent parts of the expression to go unchecked.

### [Strict mode](#strict-mode)

Strict mode is the default, and is enabled when the `strictTemplates` flag is `true` or not set.

In strict mode, Angular does the following:

* Checks embedded views (such as those within an `*ngIf` or `*ngFor`)
* Infers the correct return type of pipes
* Infers the correct type of local references to directives and pipes
* Verifies that component/directive bindings are assignable to their [`input()`](/api/core/input)s
* Obeys TypeScript's `strictNullChecks` flag when validating input bindings
* Infers the correct type of components/directives, including generics
* Infers template context types where configured (for example, allowing correct type-checking of [`NgFor`](/api/common/NgFor))
* Infers the correct type of `$event` in component/directive, DOM, and animation event bindings
* Infers the correct type of local references to DOM elements, based on the tag name (for example, the type that `document.createElement` would return for that tag)

## [Checking of `*ngFor`](#checking-of-ngfor)

The two modes of type-checking treat embedded views differently. Consider the following example.

### User interface

```
interface User {
  name: string;
  address: {
    city: string;
    state: string;
  };
}
```

```
<div *ngFor="let user of users">
  <h2>{{config.title}}</h2>
  <span>City: {{user.address.city}}</span>
</div>
```

The `<h2>` and the `<span>` are in the `*ngFor` embedded view. In basic mode, Angular doesn't check either of them. In strict mode, Angular knows that the `user` in the `<span>` has a type of `User`, and that `address` is an object with a `city` property of type `string`.

## [Troubleshooting template errors](#troubleshooting-template-errors)

With strict mode, you might encounter template errors that didn't arise in basic mode. These errors often represent genuine type mismatches in the templates that were not caught by the previous tooling. If this is the case, the error message should make it clear where in the template the problem occurs.

There can also be false positives when the typings of an Angular library are either incomplete or incorrect, or when the typings don't quite line up with expectations as in the following cases.

* When a library's typings are wrong or incomplete (for example, missing `null | undefined` if the library was not written with `strictNullChecks` in mind)

* When a library's input types are too narrow and the library hasn't added appropriate metadata for Angular to figure this out. This usually occurs with disabled or other common Boolean inputs used as attributes, for example, `<input disabled>`.

* When using `$event.target` for DOM events (because of the possibility of event bubbling, `$event.target` in the DOM typings doesn't have the type you might expect)

In case of a false positive like these, there are a few options:

* Use the `$any()` type-cast function in certain contexts to opt out of type-checking for a part of the expression
* Disable strict checks entirely by setting `strictTemplates: false` in the application's TypeScript configuration file, `tsconfig.json`
* Disable certain type-checking operations individually, while maintaining strictness in other aspects, by setting a *strictness flag* to `false`
* If you want to use `strictTemplates` and `strictNullChecks` together, opt out of strict null type checking specifically for input bindings using `strictNullInputTypes`

Each following option is set to the value for `strictTemplates` (`true` when `strictTemplates` is `true` and conversely, the other way around):

| Strictness flag              | Effect                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `strictInputTypes`           | Whether the assignability of a binding expression to the [`@Input()`](/api/core/Input) field is checked. Also affects the inference of directive generic types.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `strictNullInputTypes`       | Whether `strictNullChecks` is honored when checking [`@Input()`](/api/core/Input) bindings (per `strictInputTypes`). Turning this off can be useful when using a library that was not built with `strictNullChecks` in mind.                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `strictAttributeTypes`       | Whether to check [`@Input()`](/api/core/Input) bindings that are made using text attributes. For example, `<input matInput disabled="true">` (setting the `disabled` property to the string `'true'`) vs `<input matInput [disabled]="true">` (setting the `disabled` property to the boolean `true`).                                                                                                                                                                                                                                                                                                                                                                           |
| `strictSafeNavigationTypes`  | Whether the return type of safe navigation operations (for example, `user?.name` will be correctly inferred based on the type of `user`). If disabled, `user?.name` will be of type `any`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `strictDomLocalRefTypes`     | Whether local references to DOM elements will have the correct type. If disabled `ref` will be of type `any` for `<input #ref>`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `strictOutputEventTypes`     | Whether `$event` will have the correct type for event bindings to component/directive an [`@Output()`](/api/core/Output), or to animation events. If disabled, it will be `any`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `strictDomEventTypes`        | Whether `$event` will have the correct type for event bindings to DOM events. If disabled, it will be `any`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `strictContextGenerics`      | Whether the type parameters of generic components will be inferred correctly (including any generic bounds). If disabled, any type parameters will be `any`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `strictLiteralTypes`         | Whether object and array literals declared in the template will have their type inferred. If disabled, the type of such literals will be `any`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Strictness flag              | Effect                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------                                   |
| `strictInputAccessModifiers` | Whether access modifiers such as `private`/`protected`/`readonly` are honored when assigning a binding expression to an [`@Input()`](/api/core/Input) or [`input()`](/api/core/input). If disabled, the access modifiers of the input are ignored; only the type is checked. Note: This checking only applies to inputs, not outputs.                                                                                                                                                                                                                                                                                                                                            |
| `strictUnclaimedEventNames`  | Whether to report event bindings whose name matches neither an output of a directive applied to the element nor a known native DOM event — the ones described by TypeScript's `GlobalEventHandlersEventMap`, compared ignoring case — which usually indicates a misspelled output name. Only single-identifier camelCase names are checked; dash-separated names (for example, `my-event`), which custom events dispatched by descendants conventionally use, are exempt, as are elements without a matched component when [`CUSTOM_ELEMENTS_SCHEMA`](/api/core/CUSTOM_ELEMENTS_SCHEMA) is in use. This option is `false` by default, even with `strictTemplates` set to `true`. |

If you still have issues after troubleshooting with these flags, an option of last resort is to fall back to basic mode by disabling `strictTemplates`.

A type-checking error that you cannot resolve with any of the recommended methods can be the result of a bug in the template type-checker itself. If you get errors that require falling back to basic mode, it is likely to be such a bug. If this happens, [file an issue](https://github.com/angular/angular/issues) so the team can address it.

## [Inputs and type-checking](#inputs-and-type-checking)

The template type checker checks whether a binding expression's type is compatible with that of the corresponding directive input. As an example, consider the following component:

```
export interface User {
  name: string;
}

@Component({
  selector: 'user-detail',
  template: '{{ user().name }}',
})
export class UserDetailComponent {
  user = input.required<User>();
}
```

The `AppComponent` template uses this component as follows:

```
@Component({
  selector: 'app-root',
  template: '<user-detail [user]="selectedUser"></user-detail>',
})
export class AppComponent {
  selectedUser: User | null = null;
}
```

Here, during type checking of the template for `AppComponent`, the `[user]="selectedUser"` binding corresponds with the `UserDetailComponent.user` input. Therefore, Angular assigns the `selectedUser` property to `UserDetailComponent.user`, which would result in an error if their types were incompatible. TypeScript checks the assignment according to its type system, obeying flags such as `strictNullChecks` as they are configured in the application.

Avoid run-time type errors by providing more specific in-template type requirements to the template type checker. Make the input type requirements for your own directives as specific as possible by providing template-guard functions in the directive definition. See [Improving template type checking for custom directives](/guide/directives/structural-directives#improving-template-type-checking-for-custom-directives) in this guide.

### [Strict null checks](#strict-null-checks)

When you enable `strictTemplates` and the TypeScript flag `strictNullChecks`, typecheck errors might occur for certain situations that might not easily be avoided. For example:

* A nullable value that is bound to a directive from a library which did not have `strictNullChecks` enabled.

  For a library compiled without `strictNullChecks`, its declaration files will not indicate whether a field can be `null` or not. For situations where the library handles `null` correctly, this is problematic, as the compiler will check a nullable value against the declaration files which omit the `null` type. As such, the compiler produces a type-check error because it adheres to `strictNullChecks`.

* Using the `async` pipe with an Observable which you know will emit synchronously.

  The `async` pipe currently assumes that the Observable it subscribes to can be asynchronous, which means that it's possible that there is no value available yet. In that case, it still has to return something —which is `null`. In other words, the return type of the `async` pipe includes `null`, which might result in errors in situations where the Observable is known to emit a non-nullable value synchronously.

There are two potential workarounds to the preceding issues:

* In the template, include the non-null assertion operator `!` at the end of a nullable expression, such as

```
<user-detail [user]="user!"></user-detail>
```

In this example, the compiler disregards type incompatibilities in nullability, just as in TypeScript code. In the case of the `async` pipe, notice that the expression needs to be wrapped in parentheses, as in

```
<user-detail [user]="(user$ | async)!"></user-detail>
```

* Disable strict null checks in Angular templates completely.

  When `strictTemplates` is enabled, it is still possible to disable certain aspects of type checking. Setting the option `strictNullInputTypes` to `false` disables strict null checks within Angular templates. This flag applies for all components that are part of the application.

### [Advice for library authors](#advice-for-library-authors)

As a library author, you can take several measures to provide an optimal experience for your users. First, enabling `strictNullChecks` and including `null` in an input's type, as appropriate, communicates to your consumers whether they can provide a nullable value or not. Additionally, it is possible to provide type hints that are specific to the template type checker. See [Improving template type checking for custom directives](/guide/directives/structural-directives#improving-template-type-checking-for-custom-directives), and [Input setter coercion](#input-setter-coercion).

## [Input setter coercion](#input-setter-coercion)

Occasionally it is desirable for the [`input()`](/api/core/input) property of a directive or component to alter the value bound to it, typically using a `transform` function for the input. As an example, consider this custom button component:

Consider the following directive:

```
@Component({
  selector: 'submit-button',
  template: `
    <div class="wrapper">
      <button [disabled]="disabled()">Submit</button>
    </div>
  `,
})
class SubmitButton {
  disabled = input.required({transform: booleanAttribute});
}
```

Here, the `disabled` input of the component is being passed on to the `<button>` in the template. All of this works as expected, as long as a `boolean` value is bound to the input. But, suppose a consumer uses this input in the template as an attribute:

```
<submit-button disabled></submit-button>
```

This has the same effect as the binding:

```
<submit-button [disabled]="''"></submit-button>
```

At runtime, the input will be set to the empty string, which is not a `boolean` value. Angular component libraries that deal with this problem often "coerce" the value into the right type in the setter:

```
set disabled(value: boolean) {
  this._disabled = (value === '') || value;
}
```

It would be ideal to change the type of `value` here, from `boolean` to `boolean|''`, to match the set of values which are actually accepted by the setter. TypeScript prior to version 4.3 requires that both the getter and setter have the same type, so if the getter should return a `boolean` then the setter is stuck with the narrower type.

If the consumer has Angular's strictest type checking for templates enabled, this creates a problem: the empty string (`''`) is not actually assignable to the `disabled` field, which creates a type error when the attribute form is used.

As a workaround for this problem, Angular supports checking a wider, more permissive type for [`@Input()`](/api/core/Input) than is declared for the input field itself. Enable this by adding a static property with the `ngAcceptInputType_` prefix to the component class:

```
class SubmitButton {
  private _disabled: boolean;

  @Input()
  get disabled(): boolean {
    return this._disabled;
  }

  set disabled(value: boolean) {
    this._disabled = value === '' || value;
  }

  static ngAcceptInputType_disabled: boolean | '';
}
```

Since TypeScript 4.3, the setter could have been declared to accept `boolean|''` as type, making the input setter coercion field obsolete. As such, input setters coercion fields have been deprecated.

This field does not need to have a value. Its existence communicates to the Angular type checker that the `disabled` input should be considered as accepting bindings that match the type `boolean|''`. The suffix should be the [`@Input`](/api/core/Input) *field* name.

Care should be taken that if an `ngAcceptInputType_` override is present for a given input, then the setter should be able to handle any values of the overridden type.

## [Disabling type checking using `$any()`](#disabling-type-checking-using-any)

Disable checking of a binding expression by surrounding the expression in a call to the `$any()` cast pseudo-function. The compiler treats it as a cast to the `any` type just like in TypeScript when a `<any>` or `as any` cast is used.

In the following example, casting `person` to the `any` type suppresses the error `Property address does not exist`.

```
@Component({
  selector: 'my-component',
  template: '{{$any(person).address.street}}',
})
class MyComponent {
  person?: Person;
}
```

----
url: https://angular.dev/api/common/testing/MockLocationStrategy
----

```
    
      class MockLocationStrategy extends LocationStrategy {}
    
    
```

@paramurl`string`

@returns`void`

@paramincludeHash`boolean`

@returns`string`

@paraminternal`string`

@returns`string`

@paramctx`any`

@paramtitle`string`

@parampath`string`

@paramquery`string`

@returns`void`

@paramctx`any`

@paramtitle`string`

@parampath`string`

@paramquery`string`

@returns`void`

@paramfn`(value: any) => void`

@returns`void`

@returns`string`

@returns`void`

@returns`void`

@returns`unknown`

@paramrelativePosition`number`

@returns`void`

Jump to details

----
url: https://angular.dev/api/forms/signals/RequiredValidationError
----

```
    
      class RequiredValidationError extends BaseNgValidationError {}
    
    
```

The field associated with this error.

Human readable error message.

Jump to details

----
url: https://angular.dev/cli/run
----

```
ng run <target> [options]
```

Architect is the tool that the CLI uses to perform complex tasks such as compilation, according to provided configurations. The CLI commands run Architect targets such as `build`, `serve`, `test`, and `lint`. Each named target has a default configuration, specified by an `options` object, and an optional set of named alternate configurations in the `configurations` object.

For example, the `serve` target for a newly generated app has a predefined alternate configuration named `production`.

You can define new targets and their configuration options in the `architect` section of the `angular.json` file which you can run them from the command line using the `ng run` command.

## [Arguments](#arguments)

`target`

The Architect target to run provided in the following format `project:target[:configuration]`.

Value Type`string`

## [Options](#options)

`help`

Shows a help message for this command in the console.

Value Type`boolean`

Jump to details

----
url: https://angular.dev/guide/testing/attribute-directives
----

An *attribute directive* modifies the behavior of an element, component or another directive. Its name reflects the way the directive is applied: as an attribute on a host element.

## [Testing the `Highlight` directive](#testing-the-highlight-directive)

The sample application's `Highlight` directive sets the background color of an element based on either a data bound color or a default color (lightgray). It also sets a custom property of the element (`customProperty`) to `true` for no reason other than to show that it can.

```
import {Directive, inject, input} from '@angular/core';

/**
 * Set backgroundColor for the attached element to highlight color
 * and set the element's customProperty attribute to true
 */
@Directive({
  selector: '[highlight]',
  host: {
    '[style.backgroundColor]': 'bgColor() || defaultColor',
  },
})
export class Highlight {
  readonly defaultColor = 'rgb(211, 211, 211)'; // lightgray

  readonly bgColor = input('', {alias: 'highlight'});
}
```

It's used throughout the application, perhaps most simply in the `About` component:

```
@Component({
  imports: [Twain, Highlight],
  template: `
    <h2 highlight="skyblue">About</h2>
    <h3>Quote of the day:</h3>
    <twain-quote />
  `,
})
export class About {}
```

Testing the specific use of the `Highlight` directive within the `About` component requires only the techniques explored in the ["Nested component tests"](guide/testing/components-scenarios#nested-component-tests) section of [Component testing scenarios](guide/testing/components-scenarios).

```
let fixture: ComponentFixture<About>;

beforeEach(async () => {
  TestBed.configureTestingModule({
    providers: [TwainService, UserService],
    schemas: [CUSTOM_ELEMENTS_SCHEMA],
  });
  fixture = TestBed.createComponent(About);
  await fixture.whenStable();
});

it('should have skyblue <h2>', () => {
  const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
  const bgColor = h2.style.backgroundColor;
  expect(bgColor).toBe('skyblue');
});
```

However, testing a single use case is unlikely to explore the full range of a directive's capabilities. Finding and testing all components that use the directive is tedious, brittle, and almost as unlikely to afford full coverage.

*Class-only tests* might be helpful, but attribute directives like this one tend to manipulate the DOM. Isolated unit tests don't touch the DOM and, therefore, do not inspire confidence in the directive's efficacy.

A better solution is to create an artificial test component that demonstrates all ways to apply the directive.

```
@Component({
  imports: [Highlight],
  template: `
    <h2 highlight="yellow">Something Yellow</h2>
    <h2 highlight>The Default (Gray)</h2>
    <h2>No Highlight</h2>
    <input #box [highlight]="box.value" value="cyan" />
  `,
})
class Test {}
```

**HELPFUL:** The `<input>` case binds the `Highlight` to the name of a color value in the input box. The initial value is the word "cyan" which should be the background color of the input box.

Here are some tests of this component:

```
let fixture: ComponentFixture<Test>;
let des: DebugElement[]; // the three elements w/ the directive

beforeEach(async () => {
  fixture = TestBed.createComponent(Test);
  await fixture.whenStable();

  // all elements with an attached Highlight
  des = fixture.debugElement.queryAll(By.directive(Highlight));
});

// color tests
it('should have three highlighted elements', () => {
  expect(des.length).toBe(3);
});

it('should color 1st <h2> background "yellow"', () => {
  const bgColor = des[0].nativeElement.style.backgroundColor;
  expect(bgColor).toBe('yellow');
});

it('should color 2nd <h2> background w/ default color', () => {
  const dir = des[1].injector.get(Highlight);
  const bgColor = des[1].nativeElement.style.backgroundColor;
  expect(bgColor).toBe(dir.defaultColor);
});

it('should bind <input> background to value color', async () => {
  // easier to work with nativeElement
  const input = des[2].nativeElement as HTMLInputElement;
  expect(input.style.backgroundColor, 'initial backgroundColor').toBe('cyan');

  input.value = 'green';

  // Dispatch a DOM event so that Angular responds to the input value change.
  input.dispatchEvent(new Event('input'));
  await fixture.whenStable();

  expect(input.style.backgroundColor, 'changed backgroundColor').toBe('green');
});

it('bare <h2> should not have a backgroundColor', () => {
  // the h2 without the Highlight directive
  const bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));

  expect(bareH2.styles.backgroundColor).toBeUndefined();
});
```

A few techniques are noteworthy:

* The [`By.directive`](/api/platform-browser/By#directive) predicate is a great way to get the elements that have this directive *when their element types are unknown*

* The [`:not` pseudo-class](https://developer.mozilla.org/docs/Web/CSS/:not) in [`By.css('h2:not([highlight])')`](/api/platform-browser/By#css) helps find `<h2>` elements that *do not* have the directive. [`By.css('*:not([highlight])')`](/api/platform-browser/By#css) finds *any* element that does not have the directive.

* [`DebugElement.styles`](/api/core/DebugElement#styles) affords access to element styles even in the absence of a real browser, thanks to the [`DebugElement`](/api/core/DebugElement) abstraction. But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction.

* Angular adds a directive to the injector of the element to which it is applied. The test for the default color uses the injector of the second `<h2>` to get its `Highlight` instance and its `defaultColor`.

* [`DebugElement.properties`](/api/core/DebugElement#properties) affords access to the artificial custom property that is set by the directive

## [Testing a directive in isolation](#testing-a-directive-in-isolation)

A directive can be created on its own with [`TestBed.createDirective()`](/api/core/testing/TestBed#createDirective), which returns a [`DirectiveFixture`](/api/core/testing/DirectiveFixture). Alternatively, you can render it through a component's template. The `Highlight` directive can be tested this way, using a local test component's input to control the directive.

```
@Component({
  imports: [Highlight],
  template: `<p [highlight]="color()">{{ color() }}</p>`,
})
class Test {
  readonly color = input('');
}

describe('Highlight', () => {
  let fixture: ComponentFixture<Test>;

  beforeEach(async () => {
    fixture = TestBed.createComponent(Test);
    await fixture.whenStable();
  });

  it('should use the specified color once an input is provided', async () => {
    fixture.componentRef.setInput('color', 'blue');
    await fixture.whenStable();

    const p = fixture.nativeElement.querySelector('p');
    expect(p.style.backgroundColor).toBe('blue');
  });
});
```

----
url: https://angular.dev/api/aria/tabs/TABS
----

```
    
      const TABS: InjectionToken<Tabs>;
    
    
```

Jump to details

----
url: https://angular.dev/api/forms/FormRecord
----

```
    
      class FormRecord<TControl extends AbstractControl = AbstractControl> extends FormGroup<{  [key: string]: TControl;}> {
}
    
    
```

Registers a control with the records's list of controls.

See [`FormGroup#registerControl`](/api/forms/FormGroup#registerControl) for additional information.

@paramname`string`

@paramcontrol`TControl`

@returns`TControl`

Add a control to this group.

See [`FormGroup#addControl`](/api/forms/FormGroup#addControl) for additional information.

@paramname`string`

@paramcontrol`TControl`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Remove a control from this group.

See [`FormGroup#removeControl`](/api/forms/FormGroup#removeControl) for additional information.

@paramname`string`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Replace an existing control.

See [`FormGroup#setControl`](/api/forms/FormGroup#setControl) for additional information.

@paramname`string`

@paramcontrol`TControl`

@paramoptions`{ emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Check whether there is an enabled control with the given name in the group.

See [`FormGroup#contains`](/api/forms/FormGroup#contains) for additional information.

@paramcontrolName`string`

@returns`boolean`

Sets the value of the [`FormRecord`](/api/forms/FormRecord). It accepts an object that matches the structure of the group, with control names as keys.

See [`FormGroup#setValue`](/api/forms/FormGroup#setValue) for additional information.

@paramvalue`{ [key: string]: ɵRawValue<TControl>; }`

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Patches the value of the [`FormRecord`](/api/forms/FormRecord). It accepts an object with control names as keys, and does its best to match the values to the correct controls in the group.

See [`FormGroup#patchValue`](/api/forms/FormGroup#patchValue) for additional information.

@paramvalue`{ [key: string]: ɵValue<TControl>; }`

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; } | undefined`

@returns`void`

Resets the [`FormRecord`](/api/forms/FormRecord), marks all descendants `pristine` and `untouched` and sets the value of all descendants to null.

See [`FormGroup#reset`](/api/forms/FormGroup#reset) for additional information.

@paramvalue`{ [key: string]: ɵValue<TControl> | FormControlState<ɵValue<TControl>>; } | undefined`

@paramoptions`{ onlySelf?: boolean | undefined; emitEvent?: boolean | undefined; } | undefined`

@returns`void`

The aggregate value of the [`FormRecord`](/api/forms/FormRecord), including any disabled controls.

See [`FormGroup#getRawValue`](/api/forms/FormGroup#getRawValue) for additional information.

@returns`{ [key: string]: ɵRawValue<TControl>; }`

```
// Reference to the RequiredValidator
const ctrl = new FormControl<string | null>('', Validators.required);
ctrl.removeValidators(Validators.required);

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<string | null>('', minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)

ctrl.removeValidators(minValidator);
```

```
// Reference to the RequiredValidator
const ctrl = new FormControl<number | null>(0, Validators.required);
expect(ctrl.hasValidator(Validators.required)).toEqual(true)

// Reference to anonymous function inside MinValidator
const minValidator = Validators.min(3);
const ctrl = new FormControl<number | null>(0, minValidator);
expect(ctrl.hasValidator(minValidator)).toEqual(true)
expect(ctrl.hasValidator(Validators.min(3))).toEqual(false)
```

```
const login = new FormControl('someLogin');
login.setErrors({
  notUnique: true
});

expect(login.valid).toEqual(false);
expect(login.errors).toEqual({ notUnique: true });

login.setValue('someOtherLogin');

expect(login.valid).toEqual(true);
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

```
form = new FormGroup({
  address: new FormGroup({ street: new FormControl() })
});
```

Tracks the value and validity state of a collection of [`FormControl`](/api/forms/FormControl) instances, each of which has the same value type.

[`FormRecord`](/api/forms/FormRecord) is very similar to [`FormGroup`](/api/forms/FormGroup), except it can be used with a dynamic keys, with controls added and removed as needed.

[`FormRecord`](/api/forms/FormRecord) accepts one generic argument, which describes the type of the controls it contains.

## [Usage Notes](#usage-notes)

```
let numbers = new FormRecord({bill: new FormControl('415-123-456')});
numbers.addControl('bob', new FormControl('415-234-567'));
numbers.removeControl('bill');
```

Jump to details

----
url: https://angular.dev/api/platform-browser/withI18nSupport
----

```
function withI18nSupport(): HydrationFeature<HydrationFeatureKind.I18nSupport>;
```

@returns`HydrationFeature<HydrationFeatureKind.I18nSupport>`

Jump to details

----
url: https://angular.dev/signal-forms
----

This site uses cookies from Google to deliver its services and to analyze traffic.

[Learn more](https://policies.google.com/technologies/cookies)

main.ts

main.css

12

13

14

15

16

17

18

19

20

21

22

23

24

›

⌄

⌄

⌄

⌄

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

import {email, form, FormField, required, submit} from '@angular/forms/signals';

import {bootstrapApplication} from '@angular/platform-browser';



interface LoginData {

email: string;

password: string;

}



@Component({

selector: 'app-root',

template: \`

\<form (submit)="onSubmit($event)">

\<div>

\<label>

Email:

\<input type="email" \[formField]="loginForm.email" />

\</label>



@if (loginForm.email().invalid()) {

\<ul class="error-list">

@for (error of loginForm.email().errors(); track error) {

\<li>{{ error.message }}\</li>

}

Preview

Console

Terminal

Booting

----
url: https://angular.dev/api/core/ConstructorProvider
----

```
    
      interface ConstructorProvider extends ConstructorSansProvider {}
    
    
```

An injection token. Typically an instance of [`Type`](/api/core/Type) or [`InjectionToken`](/api/core/InjectionToken), but can be `any`.

When true, injector returns an array of instances. This is useful to allow multiple providers spread across many files to provide configuration information to a common token.

A list of `token`s to be resolved by the injector.

## [Usage Notes](#usage-notes)

```
class Square {
          name = 'square';
        }

        const injector = Injector.create({providers: [{provide: Square, deps: []}]});

        const shape: Square = injector.get(Square);
        expect(shape.name).toEqual('square');
        expect(shape instanceof Square).toBe(true);
```

### [Multi-value example](#multi-value-example)

```
const locale = new InjectionToken<string[]>('locale');
        const injector = Injector.create({
          providers: [
            {provide: locale, multi: true, useValue: 'en'},
            {provide: locale, multi: true, useValue: 'sk'},
          ],
        });

        const locales: string[] = injector.get(locale);
        expect(locales).toEqual(['en', 'sk']);
```

Jump to details

----
url: https://angular.dev/api/forms/signals/ChildFieldContext
----

## [API](/api/forms/signals/ChildFieldContext#api)

```
    
      interface ChildFieldContext<TValue> extends RootFieldContext<TValue> {
}
    
    
```

----
url: https://angular.dev/guide/incremental-hydration
----

**Incremental hydration** is an advanced type of [hydration](guide/hydration) that can leave sections of your application dehydrated and *incrementally* trigger hydration of those sections as they are needed.

## [Why use incremental hydration?](#why-use-incremental-hydration)

Incremental hydration is a performance improvement that builds on top of full application hydration. It can produce smaller initial bundles while still providing an end-user experience that is comparable to a full application hydration experience. Smaller bundles improve initial load times, reducing [First Input Delay (FID)](https://web.dev/fid) and [Cumulative Layout Shift (CLS)](https://web.dev/cls).

Incremental hydration also lets you use deferrable views (`@defer`) for content that may not have been deferrable before. Specifically, you can now use deferrable views for content that is above the fold. Prior to incremental hydration, putting a `@defer` block above the fold would result in placeholder content rendering and then being replaced by the `@defer` block's main template content. This would result in a layout shift. Incremental hydration means the main template of the `@defer` block will render with no layout shift on hydration.

## [How do you enable incremental hydration in Angular?](#how-do-you-enable-incremental-hydration-in-angular)

You can enable incremental hydration for applications that already use server-side rendering (SSR) with hydration. Follow the [Angular SSR Guide](guide/ssr) to enable server-side rendering and the [Angular Hydration Guide](guide/hydration) to enable hydration first.

Incremental hydration is enabled by default when you use [`provideClientHydration()`](/api/platform-browser/provideClientHydration).

```
import {bootstrapApplication, provideClientHydration} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration()],
});
```

**NOTE:** Incremental Hydration depends on and enables [event replay](guide/hydration#capturing-and-replaying-events) automatically. If you already have [`withEventReplay()`](/api/platform-browser/withEventReplay) in your list, you can safely remove it.

To opt out of incremental hydration, use [`withNoIncrementalHydration()`](/api/platform-browser/withNoIncrementalHydration):

```
import {
  bootstrapApplication,
  provideClientHydration,
  withNoIncrementalHydration,
} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration(withNoIncrementalHydration())],
});
```

## [How does incremental hydration work?](#how-does-incremental-hydration-work)

Incremental hydration builds on top of full-application [hydration](guide/hydration), [deferrable views](/guide/templates/defer), and [event replay](guide/hydration#capturing-and-replaying-events). With incremental hydration, you can add additional triggers to `@defer` blocks that define incremental hydration boundaries. Adding a `hydrate` trigger to a defer block tells Angular that it should load that defer block's dependencies during server-side rendering and render the main template rather than the `@placeholder`. When client-side rendering, the dependencies are still deferred, and the defer block content stays dehydrated until its `hydrate` trigger fires. That trigger tells the defer block to fetch its dependencies and hydrate the content. Any browser events, specifically those that match listeners registered in your component, that are triggered by the user prior to hydration are queued up and replayed once the hydration process is complete.

## [Controlling hydration of content with triggers](#controlling-hydration-of-content-with-triggers)

You can specify **hydrate triggers** that control when Angular loads and hydrates deferred content. These are additional triggers that can be used alongside regular `@defer` triggers.

Each `@defer` block may have multiple hydrate event triggers, separated with a semicolon (`;`). Angular triggers hydration when *any* of the triggers fire.

There are three types of hydrate triggers: `hydrate on`, `hydrate when`, and `hydrate never`.

### [`hydrate on`](#hydrate-on)

`hydrate on` specifies a condition for when hydration is triggered for the `@defer` block.

The available triggers are as follows:

| Trigger                                             | Description                                                            |
| --------------------------------------------------- | ---------------------------------------------------------------------- |
| [`hydrate on idle`](#hydrate-on-idle)               | Triggers when the browser is idle. Supports an optional timeout.       |
| [`hydrate on viewport`](#hydrate-on-viewport)       | Triggers when specified content enters the viewport                    |
| [`hydrate on interaction`](#hydrate-on-interaction) | Triggers when the user interacts with specified element                |
| [`hydrate on hover`](#hydrate-on-hover)             | Triggers when the mouse hovers over specified area                     |
| [`hydrate on immediate`](#hydrate-on-immediate)     | Triggers immediately after non-deferred content has finished rendering |
| [`hydrate on timer`](#hydrate-on-timer)             | Triggers after a specific duration                                     |

#### [`hydrate on idle`](#hydrate-on-idle)

The `hydrate on idle` trigger loads the deferrable view's dependencies and hydrates the content once the browser has reached an idle state, based on `requestIdleCallback`.

You can optionally specify a timeout in milliseconds that is passed to [`requestIdleCallback`](https://developer.mozilla.org/docs/Web/API/Window/requestIdleCallback). If the browser doesn't schedule the callback soon enough, the work will run no later than the specified timeout.

```
@defer (hydrate on idle) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

<!-- With a 500ms timeout -->
@defer (hydrate on idle(500)) {
  <large-cmp />
}
```

#### [`hydrate on viewport`](#hydrate-on-viewport)

The `hydrate on viewport` trigger loads the deferrable view's dependencies and hydrates the corresponding page of the app when the specified content enters the viewport using the [Intersection Observer API](https://developer.mozilla.org/docs/Web/API/Intersection_Observer_API).

```
@defer (hydrate on viewport) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

#### [`hydrate on interaction`](#hydrate-on-interaction)

The `hydrate on interaction` trigger loads the deferrable view's dependencies and hydrates the content when the user interacts with the specified element through `click` or `keydown` events.

```
@defer (hydrate on interaction) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

#### [`hydrate on hover`](#hydrate-on-hover)

The `hydrate on hover` trigger loads the deferrable view's dependencies and hydrates the content when the mouse has hovered over the triggered area through the `mouseover` and `focusin` events.

```
@defer (hydrate on hover) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

#### [`hydrate on immediate`](#hydrate-on-immediate)

The `hydrate on immediate` trigger loads the deferrable view's dependencies and hydrates the content immediately. This means that the deferred block loads as soon as all other non-deferred content has finished rendering.

```
@defer (hydrate on immediate) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

#### [`hydrate on timer`](#hydrate-on-timer)

The `hydrate on timer` trigger loads the deferrable view's dependencies and hydrates the content after a specified duration.

```
@defer (hydrate on timer(500ms)) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

The duration parameter must be specified in milliseconds (`ms`) or seconds (`s`).

### [`hydrate when`](#hydrate-when)

The `hydrate when` trigger accepts a custom conditional expression and loads the deferrable view's dependencies and hydrates the content when the condition becomes truthy.

```
@defer (hydrate when condition) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

**NOTE:** `hydrate when` conditions only trigger when they are the top-most dehydrated `@defer` block. The condition provided for the trigger is specified in the parent component, which needs to exist before it can be triggered. If the parent block is dehydrated, that expression will not yet be resolvable by Angular.

### [`hydrate never`](#hydrate-never)

The `hydrate never` allows users to specify that the content in the defer block should remain dehydrated indefinitely, effectively becoming static content. Note that this applies to the initial render only. During a subsequent client-side render, a `@defer` block with `hydrate never` would still fetch dependencies, as hydration only applies to initial load of server-side rendered content. In the example below, subsequent client-side renders would load the `@defer` block dependencies on viewport.

```
@defer (on viewport; hydrate never) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}
```

**NOTE:** Using `hydrate never` prevents hydration of the entire nested subtree of a given `@defer` block. No other `hydrate` triggers fire for content nested underneath that block.

## [Hydrate triggers alongside regular triggers](#hydrate-triggers-alongside-regular-triggers)

Hydrate triggers are additional triggers that are used alongside regular triggers on a `@defer` block. Hydration is an initial load optimization, and that means hydrate triggers only apply to that initial load. Any subsequent client side render will still use the regular trigger.

```
@defer (on idle; hydrate on interaction) {
  <example-cmp />
} @placeholder {
  <div>Example Placeholder</div>
}
```

In this example, on the initial load, the `hydrate on interaction` applies. Hydration will be triggered on interaction with the `<example-cmp />` component. On any subsequent page load that is client-side rendered, for example when a user clicks a routerLink that loads a page with this component, the `on idle` will apply.

## [How does incremental hydration work with nested `@defer` blocks?](#how-does-incremental-hydration-work-with-nested-defer-blocks)

Angular's component and dependency system is hierarchical, which means hydrating any component requires all of its parents also be hydrated. So if hydration is triggered for a child `@defer` block of a nested set of dehydrated `@defer` blocks, hydration is triggered from the top-most dehydrated `@defer` block down to the triggered child and fire in that order.

```
@defer (hydrate on interaction) {
  <parent-block-cmp />
  @defer (hydrate on hover) {
    <child-block-cmp />
  } @placeholder {
    <div>Child placeholder</div>
  }
} @placeholder {
  <div>Parent Placeholder</div>
}
```

In the above example, hovering over the nested `@defer` block triggers hydration. The parent `@defer` block with the `<parent-block-cmp />` hydrates first, then the child `@defer` block with `<child-block-cmp />` hydrates after.

## [Constraints](#constraints)

Incremental hydration has the same constraints as full-application hydration, including limits on direct DOM manipulation and requiring valid HTML structure. Visit the [Hydration guide constraints](guide/hydration#constraints) section for more details.

## [Do I still need to specify `@placeholder` blocks?](#do-i-still-need-to-specify-placeholder-blocks)

Yes. `@placeholder` block content is not used for incremental hydration, but a `@placeholder` is still necessary for subsequent client-side rendering cases. If your content was not on the route that was part of the initial load, then any navigation to the route that has your `@defer` block content renders like a regular `@defer` block. So the `@placeholder` is rendered in those client-side rendering cases.

----
url: https://angular.dev/api/core/DebugNode
----

```
    
      class DebugNode {}
    
    
```

@paramnativeNode`Node`

@returns`DebugNode`

----
url: https://angular.dev/api/core/WebMcpClient
----

```
    
      interface WebMcpClient {}
    
    
```

A signal which notifies the tool when the operation is aborted. When triggered, the current operation should be canceled and all allocated resources should be cleaned up.

Jump to details

----
url: https://angular.dev/api/forms/signals/validateHttp
----

```
function validateHttp<
  TValue,
  TResult = unknown,
  TPathKind extends PathKind = PathKind.Root,
>(
  path: SchemaPath<TValue, 1, TPathKind>,
  opts: HttpValidatorOptions<TValue, TResult, TPathKind>,
): void;
```

@parampath`SchemaPath<TValue, 1, TPathKind>`

A path indicating the field to bind the async validation logic to.

@paramopts`HttpValidatorOptions<TValue, TResult, TPathKind>`

The http validation options.

@returns`void`

Jump to details

----
url: https://angular.dev/guide/templates/variables
----

Angular has two types of variable declarations in templates: local template variables and template reference variables.

**HELPFUL:** In this guide, “template” does not mean the entire HTML template file. It refers only to a specific template construct or expression within the file.

## [Local template variables with `@let`](#local-template-variables-with-let)

Angular's `@let` syntax allows you to define a local variable and re-use it across a template, similar to the [JavaScript `let` syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let).

### [Using `@let`](#using-let)

Use `@let` to declare a variable whose value is based on the result of a template expression. Angular automatically keeps the variable's value up-to-date with the given expression, similar to [bindings](/guide/templates/binding).

```
@let name = user.name;
@let greeting = 'Hello, ' + name;
@let data = data$ | async;
@let pi = 3.14159;
@let coordinates = {x: 50, y: 100};
@let longExpression =
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit ' +
  'sed do eiusmod tempor incididunt ut labore et dolore magna ' +
  'Ut enim ad minim veniam...';
```

Each `@let` block can declare exactly one variable. You cannot declare multiple variables in the same block with a comma.

### [Referencing the value of `@let`](#referencing-the-value-of-let)

Once you've declared a variable with `@let`, you can reuse it in the same template:

```
@let user = user$ | async;

@if (user) {
  <h1>Hello, {{ user.name }}</h1>
  <user-avatar [photo]="user.photo" />

  <ul>
    @for (snack of user.favoriteSnacks; track snack.id) {
      <li>{{ snack.name }}</li>
    }
  </ul>

  <button (click)="update(user)">Update profile</button>
}
```

### [Assignability](#assignability)

A key difference between `@let` and JavaScript's `let` is that `@let` cannot be reassigned after declaration. However, Angular automatically keeps the variable's value up-to-date with the given expression.

```
@let value = 1;

<!-- Invalid - This does not work! -->
<button (click)="value = value + 1">Increment the value</button>
```

### [Variable scope](#variable-scope)

`@let` declarations are scoped to the current view and its descendants. Angular creates a new view at component boundaries and wherever a template might contain dynamic content, such as control flow blocks, `@defer` blocks, or structural directives.

Since `@let` declarations are not hoisted, they **cannot** be accessed by parent views or siblings:

```
@let topLevel = value;

<div>
  @let insideDiv = value;
</div>

<!-- Valid -->
{{ topLevel }}
<!-- Valid -->
{{ insideDiv }}

@if (condition) {
  <!-- Valid -->
  {{ topLevel + insideDiv }}

  @let nested = value;

  @if (condition) {
    <!-- Valid -->
    {{ topLevel + insideDiv + nested }}
  }
}

<!-- Error, not hoisted from @if -->
{{ nested }}
```

```
<!-- Create a template reference variable named "taskInput", referring to the HTMLInputElement. -->
<input #taskInput placeholder="Enter task name" />
```

### [Assigning values to template reference variables](#assigning-values-to-template-reference-variables)

Angular assigns a value to template variables based on the element on which the variable is declared.

If you declare the variable on a Angular component, the variable refers to the component instance.

```
<!-- The `startDate` variable is assigned the instance of `MyDatepicker`. -->
<my-datepicker #startDate />
```

If you declare the variable on an `<ng-template>` element, the variable refers to a TemplateRef instance which represents the template. For more information, see [How Angular uses the asterisk, \*, syntax](/guide/directives/structural-directives#structural-directive-shorthand) in [Structural directives](/guide/directives/structural-directives).

```
<!-- The `myFragment` variable is assigned the `TemplateRef` instance corresponding to this template fragment. -->
<ng-template #myFragment>
  <p>This is a template fragment</p>
</ng-template>
```

If you declare the variable on any other displayed element, the variable refers to the `HTMLElement` instance.

```
<!-- The "taskInput" variable refers to the HTMLInputElement instance. -->
<input #taskInput placeholder="Enter task name" />
```

#### [Assigning a reference to an Angular directive](#assigning-a-reference-to-an-angular-directive)

Angular directives may have an `exportAs` property that defines a name by which the directive can be referenced in a template:

```
@Directive({
  selector: '[dropZone]',
  exportAs: 'dropZone',
})
export class DropZone {
  /* ... */
}
```

When you declare a template variable on an element, you can assign that variable a directive instance by specifying this `exportAs` name:

```
<!-- The `firstZone` variable refers to the `DropZone` directive instance. -->
<section dropZone #firstZone="dropZone">...</section>
```

You cannot refer to a directive that does not specify an `exportAs` name.

### [Using template reference variables with queries](#using-template-reference-variables-with-queries)

In addition to using template variables to read values from another part of the same template, you can also use this style of variable declaration to "mark" an element for [component and directive queries](/guide/components/queries).

When you want to query for a specific element in a template, you can declare a template variable on that element and then query for the element based on the variable name.

```
<input #description value="Original description" />
```

```
@Component({
  /* ... */,
  template: `<input #description value="Original description">`,
})
export class AppComponent {
  // Query for the input element based on the template variable name.
  @ViewChild('description') input: ElementRef | undefined;
}
```

```
<input #ref1 type="text" [(ngModel)]="firstExample" />

@if (true) {
  <span>Value: {{ ref1.value }}</span>
}
```

In this case, the `@if` block creates a new template scope, which includes the `ref1` variable from its parent scope.

However, accessing a template variable from a child scope in the parent template doesn't work:

Avoid

```
@if (true) {
  <input #ref2 type="text" [(ngModel)]="secondExample" />
}

<span>Value: {{ ref2?.value }}</span>
```

Here, `ref2` is declared in the child scope created by `@if`, and is not accessible from the parent template.

----
url: https://angular.dev/api/core/testing/InjectSetupWrapper
----

```
    
      class InjectSetupWrapper {}
    
    
```

@param\_moduleDef`() => TestModuleMetadata`

@returns`InjectSetupWrapper`

@paramtokens`any[]`

@paramfn`Function`

@returns`() => any`

Jump to details

----
url: https://angular.dev/api/cdk/testing/testbed/TestbedHarnessEnvironmentOptions
----

```
    
      interface TestbedHarnessEnvironmentOptions {}
    
    
```

The query function used to find DOM elements.

Jump to details