Skip to content

Instantly share code, notes, and snippets.

@rajinder-yadav
Last active September 25, 2025 01:25
Show Gist options
  • Select an option

  • Save rajinder-yadav/7dae20287bc2a7fbead74d050badc0f7 to your computer and use it in GitHub Desktop.

Select an option

Save rajinder-yadav/7dae20287bc2a7fbead74d050badc0f7 to your computer and use it in GitHub Desktop.

Prerequisites

Instructions

The following guide will walk you through setting up a local Angular project.

Install Angular CLI

Open a terminal (if you're using Visual Studio Code, you can open an 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 for more info.

Create a new project

In your terminal, run the CLI command ng 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

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

To get started with building in your preferred AI powered IDE, check out Angular prompt rules and best practices.

Next steps

Now that you've created your Angular project, you can learn more about Angular in our Essentials guide or choose a topic in our in-depth guides!

Angular coding 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.

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

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

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

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

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

All the application's 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

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

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

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

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

Prefer the inject function over constructor parameter injection

Prefer using the inject function over injecting constructor parameters. The inject function works the same way as constructor parameter injection, but offers several style advantages:

  • inject is generally more readable, especially when a class injects many dependencies.
  • It's more syntactically straightforward to add comments to injected dependencies
  • inject offers better type inference.
  • When targeting ES2022+ with 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.

Components and directives

Choosing component selectors

See the Components guide for details on choosing component selectors.

Naming component and directive members

See the Components guide for details on naming input properties and naming output properties.

Choosing directive selectors

Directives should use the same application-specific prefix 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

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

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

Angular templates are designed to accommodate JavaScript-like expressions. 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).

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

A component class's public members intrinsically define a public API that's accessible via dependency injection and 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

Mark component and directive properties initialized by Angular as readonly. This includes properties initialized by input, model, 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, @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

Prefer class and style bindings over using the NgClass and NgStyle directives.

<!-- PREFER -->
<div [class.admin]="isAdmin" [class.dense]="density === 'high'">
<!-- OR -->
<div [class]="{admin: isAdmin, dense: density === 'high'}">
<!-- AVOID -->
<div [ngClass]="{admin: isAdmin, dense: density === 'high'}">

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 and 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

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

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

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() { /* ... */ }
}

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

Every component has a few main parts:

  1. A @Componentdecorator that contains some configuration used by Angular.
  2. An HTML template that controls what renders into the DOM.
  3. A CSS selector 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 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

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

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:

flowchart TD
    A[UserProfile]-->B
    A-->C
    B[UserBiography]-->D
    C[ProfilePhoto]
    D[UserAddress]
Loading

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 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.ts';

@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 for the full details.

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.

Component selectors

TIP: This guide assumes you've already read the Essentials Guide. Read that first if you're new to Angular.

Every component defines a CSS selector 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

Angular supports a limited subset of basic CSS selector types 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 or child combinator.

Angular component selectors do not support specifying namespaces.

The :not pseudo-class

Angular supports the :not pseudo-class. 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

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

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. 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 for details on using native custom elements in Angular templates.

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

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 for more information.

Components that define attribute selectors should use lowercase, dash-case attributes. You can follow the same prefixing recommendation described above.

Styling components

TIP: This guide assumes you've already read the Essentials Guide. Read that first if you're new to Angular.

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, less, and stylus.

Style scoping

Every component has a view encapsulation setting that determines how the framework scopes a component's styles. There are three view encapsulation modes: Emulated, ShadowDom, and None. You can specify the mode in the @Component decorator:

@Component({
  ...,
  encapsulation: ViewEncapsulation.None,
})
export class ProfilePhoto { }

ViewEncapsulation.Emulated

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 and :host-context() pseudo classes without 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

Angular's emulated encapsulation mode supports a custom pseudo class, ::ng-deep. Applying this pseudo class to a CSS rule disables encapsulation for that rule, effectively turning it into a global style. The Angular team strongly discourages new use of ::ng-deep. These APIs remain exclusively for backwards compatibility.

ViewEncapsulation.ShadowDom

This mode scopes styles within a component by using the web standard Shadow DOM API. 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.

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.

Enabling ShadowDom encapsulation, however, impacts more than style scoping. Rendering the component in a shadow tree affects event propagation, interaction with the <slot> API, and how browser developer tools show elements. Always understand the full implications of using Shadow DOM in your application before enabling this option.

ViewEncapsulation.None

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

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

Component templates can use the <link> element to reference CSS files. Additionally, your CSS may use the @importat-rule to reference CSS files. Angular treats these references as external styles. External styles are not affected by emulated view encapsulation.

Accepting data with input properties

TIP: This guide assumes you've already read the Essentials Guide. Read that first if you're new to Angular.

TIP: 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

The input function returns an InputSignal. You can read the value by calling the signal:

import {Component, input} 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

You can declare that an input is required by calling input.required 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.

Configuring inputs

The input function accepts a config object as a second parameter that lets you change the way that input works.

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. Relying on state outside the transform function can lead to unpredictable behavior.

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 property returns a string.

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 imitates the behavior of standard HTML boolean attributes, where the presence of the attribute indicates a "true" value. However, Angular's booleanAttribute treats the literal string "false" as the boolean false.

numberAttribute attempts to parse the given value to a number, producing NaN if parsing fails.

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 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 like computed and effect.

See Two-way binding for more details on two-way binding in templates.

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

When you declare a model input in a component or directive, Angular automatically creates a corresponding output 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 for more details on outputs.

Customizing model inputs

You can mark a model input as required or provide an alias in the same way as a standard input.

Model inputs do not support input transforms.

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

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

TIP: While the Angular team recommends using the signal-based input function for new projects, the original decorator-based @Input API remains fully supported.

You can alternatively declare component inputs by adding the @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

The @Input decorator accepts a config object that lets you change the way that input works.

Required inputs

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

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

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 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

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

In addition to the @Input decorator, you can also specify a component's inputs with the inputs property in the @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 { }

Custom events with outputs

TIP: This guide assumes you've already read the Essentials Guide. Read that first if you're new to Angular.

Angular components can define custom events by assigning a property to the output function:

@Component({/*...*/})
export class ExpandablePanel {
  panelClosed = output<void>();
}
<expandable-panel (panelClosed)="savePanelState()" />

The output function returns an OutputEmitterRef. You can emit an event by calling the emit method on the OutputEmitterRef:

this.panelClosed.emit();

Angular refers to properties initialized with the 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 function has special meaning to the Angular compiler. You can exclusively call output in component and directive property initializers.

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

The 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

When creating a component dynamically, you can programmatically subscribe to output events from the component instance. The 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 with an unsubscribe method:

const eventSubscription = someComponent.someEventProperty.subscribe(eventData => {
  console.log(eventData);
});

// ...

eventSubscription.unsubscribe();

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 output names. Avoid prefixing output names with "on".

Using outputs with RxJS

See RxJS interop with component and directive outputs for details on interoperability between outputs and RxJS.

Declaring outputs with the @Output decorator

TIP: While the Angular team recommends using the output function for new projects, the original decorator-based @Output API remains fully supported.

You can alternatively define custom events by assigning a property to a new EventEmitter and adding the @Output decorator:

@Component({/*...*/})
export class ExpandablePanel {
  @Output() panelClosed = new EventEmitter<void>();
}

You can emit an event by calling the emit method on the EventEmitter.

Aliases with the @Output decorator

The @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

In addition to the @Output decorator, you can also specify a component's outputs with the outputs property in the @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 {}

Content projection with ng-content

TIP: This guide assumes you've already read the Essentials Guide. Read that first if you're new to Angular.

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></ng-content> </div>',
})
export class CustomCard {/* ... */}

TIP: <ng-content> works similarly to the native <slot> element, 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.

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"></ng-content>
    <div class="card-divider"></div>
    <ng-content select="card-body"></ng-content>
  </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.

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"></ng-content>
  <div class="card-divider"></div>
  <!-- capture anything except "card-title" -->
  <ng-content></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

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

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"></ng-content>
  <div class="card-divider"></div>
  <ng-content></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.

Component Lifecycle

TIP: This guide assumes you've already read the Essentials Guide. Read that first if you're new to Angular.

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

Phase Method Summary
Creation constructor Standard JavaScript class 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

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

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

The ngOnChanges method accepts one SimpleChanges argument. This object is a Record mapping each component input name to a SimpleChange object. Each SimpleChange contains the input's previous value, its current value, and a flag for whether this is the first time the input has changed.

@Component({
  /* ... */
})
export class UserProfile {
  name = input('');

  ngOnChanges(changes: SimpleChanges) {
    for (const inputName in changes) {
      const inputValues = changes[inputName];
      console.log(`Previous ${inputName} == ${inputValues.previousValue}`);
      console.log(`Current ${inputName} == ${inputValues.currentValue}`);
      console.log(`Is first ${inputName} change == ${inputValues.firstChange}`);
    }
  }
}

If you provide an alias for any input properties, the SimpleChanges Record still uses the TypeScript property name as a key, rather than the alias.

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

As an alternative to the ngOnDestroy method, you can inject an instance of DestroyRef. You can register a callback to be invoked upon the component's destruction by calling the onDestroy method of DestroyRef.

@Component({
  /* ... */
})
export class UserProfile {
  constructor() {
    inject(DestroyRef).onDestroy(() => {
      console.log('UserProfile destruction');
    });
  }
}

You can pass the 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 to keep setup code close to cleanup code, rather than putting all cleanup code in the ngOnDestroy method.

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

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. While you can access the initialized state of these queries, attempting to change any state in this method results in an ExpressionChangedAfterItHasBeenCheckedError

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 here, attempting to change any state in this method results in an ExpressionChangedAfterItHasBeenCheckedError.

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. While you can access the initialized state of these queries, attempting to change any state in this method results in an ExpressionChangedAfterItHasBeenCheckedError

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 here, attempting to change any state in this method results in an ExpressionChangedAfterItHasBeenCheckedError.

afterEveryRender and afterNextRender

The afterEveryRender and 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 and afterNextRender must be called in an injection context, typically a component's constructor.

You can use render callbacks to perform manual DOM operations. See Using 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

When using afterEveryRender or 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. 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() {
    private 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.
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.
write Use this phase to write layout-affecting DOM properties and styles.
read Use this phase to read any layout-affecting DOM properties.

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.

@Component({
  /* ... */
})
export class UserProfile implements OnInit {
  ngOnInit() {
    /* ... */
  }
}

Execution order

The following diagrams show the execution order of Angular's lifecycle hooks.

During initialization

graph TD;
id[constructor]-->CHANGE;
subgraph CHANGE [Change detection]
direction TB
ngOnChanges-->ngOnInit;
ngOnInit-->ngDoCheck;
ngDoCheck-->ngAfterContentInit;
ngDoCheck-->ngAfterViewInit
ngAfterContentInit-->ngAfterContentChecked
ngAfterViewInit-->ngAfterViewChecked
end
CHANGE--Rendering-->afterNextRender-->afterEveryRender
Loading

Subsequent updates

graph TD;
subgraph CHANGE [Change detection]
direction TB
ngOnChanges-->ngDoCheck
ngDoCheck-->ngAfterContentChecked;
ngDoCheck-->ngAfterViewChecked
end
CHANGE--Rendering-->afterEveryRender
Loading

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. 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

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 TodoListItem {
  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

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()">

Angular automatically updates DOM properties and attributes when the bound value changes.

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 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

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's 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? for more info.

TIP: Want to know more about Angular templates? See the In-depth Templates guide for the full details.

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.

Adding 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

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 AppComponent{
  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.

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 AppComponent {
  updateField(event: KeyboardEvent): void {
    console.log(`The user pressed: ${event.key}`);
  }
}

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 AppComponent {
  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 AppComponent{
  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.

Angular also allows you to specify Code values for keyboard events 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.

Preventing event default behavior

If your event handler should replace the native browser behavior, you can use the event object's preventDefault method:

@Component({
  template: `
    <a href="#overlay" (click)="showOverlay($event)">
  `,
  ...
})
export class AppComponent{
  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. Always prefer explicitly calling preventDefault, as this approach makes the code's intent obvious.

Binding dynamic text, properties and attributes

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

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 AppComponent {
  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. 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 `name` signal changes. -->
  `
  ...
})
export class AppComponent {
  welcomeMessage = "Welcome, enjoy this app that we built for you"; 
  theme = signal('dark');
}

For more details, see the Signals guide.

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>

You can use text interpolation anywhere you would normally write text in HTML.

All expression values are converted to a string. Objects and arrays are converted using the value’s toString method.

Binding dynamic properties and attributes

Angular supports binding dynamic values into object properties and HTML attributes with square brackets.

You can bind to properties on an HTML element's DOM instance, a component instance, or a directive instance.

Native element properties

Every HTML element has a corresponding DOM representation. For example, each <button> HTML element corresponds to an instance of HTMLButtonElement in the DOM. In Angular, you use property bindings to set values directly to the DOM representation of the element.

<!-- 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

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

When you need to set HTML attributes that do not have corresponding DOM properties, such as ARIA attributes or 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

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() }}" >

To bind to an attribute with the text interpolation syntax, prefix the attribute name with attr.

<button attr.aria-label="Save changes to {{ objectType() }}">

CSS class and style property bindings

Angular supports additional features for binding CSS classes and CSS style properties to elements.

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.

<!-- 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 directive.

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 to 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.

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

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

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

The @for block loops through a collection and repeatedly renders the content of a block. The collection can be any JavaScript iterable, 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?

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 specify identity. 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.

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

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 aria-hidden="true"> There are no items. </li>
}

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') {
    <app-reviewer-dashboard />
  }
  @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 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.

Variables in templates

Angular has two types of variable declarations in templates: local template variables and template reference variables.

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.

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.

@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

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

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

@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>

{{topLevel}} <!-- Valid -->
{{insideDiv}} <!-- Valid -->

@if (condition) {
  {{topLevel + insideDiv}} <!-- Valid -->

  @let nested = value;

  @if (condition) {
    {{topLevel + insideDiv + nested}} <!-- Valid -->
  }
}

{{nested}} <!-- Error, not hoisted from @if -->

Full syntax

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.

Template reference variables

Template reference variables give you a way to declare a variable that references a value from an element in your template.

A template reference variable can refer to the following:

You can use template reference variables to read information from one part of the template in another part of the same template.

Declaring a template reference variable

You can declare a variable on an element in a template by adding an attribute that starts with the hash character (#) followed by the variable name.

<!-- Create a template reference variable named "taskInput", referring to the HTMLInputElement. -->
<input #taskInput placeholder="Enter task name">

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 in 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

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

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.

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;
}

See Referencing children with queries for more information on queries.

Deferred loading with @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?

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 and participate in deferred loading.

Angular's compiler produces a dynamic 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

@defer blocks have several sub blocks to allow you to gracefully handle different stages in the deferred loading process.

@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 occurs or the when condition is met.

By default, a @defer block is triggered when the browser state becomes idle.

@defer {
  <large-component />
}

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 to function. See the 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

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

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

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

on specifies a condition for when the @defer block is triggered.

The available triggers are as follows:

Trigger Description
idle Triggers when the browser is idle.
viewport Triggers when specified content enters the viewport
interaction Triggers when the user interacts with specified element
hover Triggers when the mouse hovers over specified area
immediate Triggers immediately after non-deferred content has finished rendering
timer Triggers after a specific duration

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.

<!-- @defer (on idle) -->
@defer {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

viewport

The viewport trigger loads the deferred content when the specified content enters the viewport using the 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 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 />
}

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 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 viewport trigger.

<div #greeting>Hello!</div>
@defer (on interaction(greeting)) {
  <greetings-cmp />
}

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 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 hover(greeting)) {
  <greetings-cmp />
}

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

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

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

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>
}

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 ComponentA {}
  // Create component fixture.
  const componentFixture = TestBed.createComponent(ComponentA);
  // 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?

@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)

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)?

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 and configure hydrate triggers for the necessary blocks.

Best practices for deferring views

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

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.

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

Angular supports a subset of literal values from JavaScript.

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
Template string `Hello ${name}`

Unsupported literals

Literal type Example value
RegExp /\d+/

Unsupported value literals

Literal type Example values
BigInt 1n

Globals

Angular expressions support the following globals:

No other JavaScript globals are supported. Common JavaScript globals include Number, Boolean, NaN, Infinity, parseInt, and more.

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?

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']
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

Angular expressions additionally also support the following non-standard operators:

Operator Example(s)
Pipe {{ total | currency }}
Optional chaining* someObj.someProp?.nestedProp
Non-null assertion (TypeScript) someObj!.someProp

NOTE: Optional chaining behaves differently from the standard JavaScript version in that if the left side of Angular’s optional chaining operator is null or undefined, it returns null instead of undefined.

Unsupported operators

Operator Example(s)
All bitwise operators &, &=, ~, |=, ^=, etc.
Object destructuring const { name } = person
Array destructuring const [firstItem] = items
Comma operator x = (x++, x)
instanceof car instanceof Automobile
new new Car()

Lexical context for expressions

Angular expressions are evaluated within the context of the component class as well as any relevant template variables, locals, and globals.

When referring to component class members, this is always implied. However, if a template declares a template 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

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 () => { }
Classes class Rectangle { }

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 destructing assignments)
  2. Statements do not support pipes

Attribute directives

Change the appearance or behavior of DOM elements and Angular components with attribute directives.

Building an attribute directive

This section walks you through creating a highlight directive that sets the background color of the host element to yellow.

  1. To create a directive, use the CLI command ng generate directive.

ng generate directive highlight

    The CLI creates `src/app/highlight.directive.ts`, a corresponding test file `src/app/highlight.directive.spec.ts`.

    <docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.0.ts"/>

    The `@Directive()` decorator's configuration property specifies the directive's CSS attribute selector, `[appHighlight]`.

1. Import `ElementRef` from `@angular/core`.
    `ElementRef` grants direct access to the host DOM element through its `nativeElement` property.

1. Add `ElementRef` in the directive's `constructor()` to [inject](guide/di) a reference to the host DOM element, the element to which you apply `appHighlight`.

1. Add logic to the `HighlightDirective` class that sets the background to yellow.

    <docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.1.ts"/>

HELPFUL: Directives *do not* support namespaces.

<docs-code header="src/app/app.component.avoid.html (unsupported)" path="adev/src/content/examples/attribute-directives/src/app/app.component.avoid.html" visibleRegion="unsupported"/>

## Applying an attribute directive

1. To use the `HighlightDirective`, add a `<p>` element to the HTML template with the directive as an attribute.

    <docs-code header="src/app/app.component.html" path="adev/src/content/examples/attribute-directives/src/app/app.component.1.html" visibleRegion="applied"/>

Angular creates an instance of the `HighlightDirective` class and injects a reference to the `<p>` element into the directive's constructor, which sets the `<p>` element's background style to yellow.

## Handling user events

This section shows you how to detect when a user mouses into or out of the element and to respond by setting or clearing the highlight color.

1. Import `HostListener` from '@angular/core'.

    <docs-code header="src/app/highlight.directive.ts (imports)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts" visibleRegion="imports"/>

1. Add two event handlers that respond when the mouse enters or leaves, each with the `@HostListener()` decorator.

    <docs-code header="src/app/highlight.directive.ts (mouse-methods)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts" visibleRegion="mouse-methods"/>

Subscribe to events of the DOM element that hosts an attribute directive, the `<p>` in this case, with the `@HostListener()` decorator.

HELPFUL: The handlers delegate to a helper method, `highlight()`, that sets the color on the host DOM element, `el`.

The complete directive is as follows:

<docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.2.ts"/>

The background color appears when the pointer hovers over the paragraph element and disappears as the pointer moves out.

<img alt="Second Highlight" src="assets/images/guide/attribute-directives/highlight-directive-anim.gif">

## Passing values into an attribute directive

This section walks you through setting the highlight color while applying the `HighlightDirective`.

1. In `highlight.directive.ts`, import `Input` from `@angular/core`.

    <docs-code header="src/app/highlight.directive.ts (imports)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="imports"/>

1. Add an `appHighlight` `input` property.

    <docs-code header="src/app/highlight.directive.ts" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="input"/>

    The `input()` function adds metadata to the class that makes the directive's `appHighlight` property available for binding.

2. In `app.component.ts`, add a `color` property to the `AppComponent`.

    <docs-code header="src/app/app.component.ts (class)" path="adev/src/content/examples/attribute-directives/src/app/app.component.1.ts" visibleRegion="class"/>

3. To simultaneously apply the directive and the color, use property binding with the `appHighlight` directive selector, setting it equal to `color`.

    <docs-code header="src/app/app.component.html (color)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="color"/>

    The `[appHighlight]` attribute binding performs two tasks:

    * Applies the highlighting directive to the `<p>` element
    * Sets the directive's highlight color with a property binding

### Setting the value with user input

This section guides you through adding radio buttons to bind your color choice to the `appHighlight` directive.

1. Add markup to `app.component.html` for choosing a color as follows:

    <docs-code header="src/app/app.component.html (v2)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="v2"/>

1. Revise the `AppComponent.color` so that it has no initial value.

    <docs-code header="src/app/app.component.ts (class)" path="adev/src/content/examples/attribute-directives/src/app/app.component.ts" visibleRegion="class"/>

1. In `highlight.directive.ts`, revise `onMouseEnter` method so that it first tries to highlight with `appHighlight` and falls back to `red` if `appHighlight` is `undefined`.

    <docs-code header="src/app/highlight.directive.ts (mouse-enter)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.3.ts" visibleRegion="mouse-enter"/>

1. Serve your application to verify that the user can choose the color with the radio buttons.

    <img alt="Animated gif of the refactored highlight directive changing color according to the radio button the user selects" src="assets/images/guide/attribute-directives/highlight-directive-v2-anim.gif">

## Binding to a second property

This section guides you through configuring your application so the developer can set the default color.

1. Add a second `Input()` property to `HighlightDirective` called `defaultColor`.

    <docs-code header="src/app/highlight.directive.ts (defaultColor)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts" visibleRegion="defaultColor"/>

1. Revise the directive's `onMouseEnter` so that it first tries to highlight with the `appHighlight`, then with the `defaultColor`, and falls back to `red` if both properties are `undefined`.

    <docs-code header="src/app/highlight.directive.ts (mouse-enter)" path="adev/src/content/examples/attribute-directives/src/app/highlight.directive.ts" visibleRegion="mouse-enter"/>

1. To bind to the `AppComponent.color` and fall back to "violet" as the default color, add the following HTML.
    In this case,  the `defaultColor` binding doesn't use square brackets, `[]`, because it is static.

    <docs-code header="src/app/app.component.html (defaultColor)" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="defaultColor"/>

    As with components, you can add multiple directive property bindings to a host element.

The default color is red if there is no default color binding.
When the user chooses a color the selected color becomes the active highlight color.

<img alt="Animated gif of final highlight directive that shows red color with no binding and violet with the default color set. When user selects color, the selection takes precedence." src="assets/images/guide/attribute-directives/highlight-directive-final-anim.gif">

## 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`.

<docs-code header="src/app/app.component.html" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="ngNonBindable"/>

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 }}`.

<docs-code header="src/app/app.component.html" path="adev/src/content/examples/attribute-directives/src/app/app.component.html" visibleRegion="ngNonBindable-with-directive"/>

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.
# Structural directives

Structural directives are directives applied to an `<ng-template>` element that conditionally or repeatedly render the content of that `<ng-template>`.

## Example use case

In this guide you'll build a structural directive which fetches data from a given data source and renders its template when that data is available. This directive is called `SelectDirective`, after the SQL keyword `SELECT`, and match it with an attribute selector `[select]`.

`SelectDirective` will have an input naming the data source to be used, which you will call `selectFrom`. The `select` prefix for this input is important for the [shorthand syntax](#structural-directive-shorthand). The directive will instantiate its `<ng-template>` with a template context providing the selected data.

The following is an example of using this directive directly on an `<ng-template>` would look like:

```html
<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: Note that Angular's <ng-template> element defines a template that doesn't render anything by default, if you just wrap elements in an <ng-template> without applying a structural directive those elements will not be rendered.

For more information, see the ng-template API documentation.

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 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

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 when 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

This section guides you through creating the SelectDirective.

Using the Angular CLI, run the following command, where `select` is the name of the directive:
ng generate directive select

Angular creates the directive class and specifies the CSS selector, [select], that identifies the directive in a template. Import TemplateRef, and ViewContainerRef. Inject TemplateRef and ViewContainerRef in the directive as private properties.

import {Directive, TemplateRef, ViewContainerRef} from '@angular/core';

@Directive({
  selector: '[select]',
})
export class SelectDirective {
  private templateRef = inject(TemplateRef);
  private viewContainerRef = inject(ViewContainerRef);
}
Add a `selectFrom` `input()` property.
export class SelectDirective {
  // ...

  selectFrom = input.required<DataSource>();
}
With `SelectDirective` now scaffolded as a structural directive with its input, you can now add the logic to fetch the data and render the template with it:
export class SelectDirective {
  // ...

  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,
    });
  }
}

That's it - SelectDirective is up and running. A follow-up step might be to add template type-checking support.

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

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

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

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.

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

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;
  }
}

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

You apply directives to a component by adding a hostDirectives property to a component's decorator. We call such directives host directives.

In this example, we apply the directive MenuBehavior 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',
  template: '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 below for more information.

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

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',
  template: '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 hostDirective can bind them in a template:

<admin-menu menuId="top-menu" (menuClosed)="logMenuClosed()">

Furthermore, you can alias inputs and outputs from hostDirective to customize the API of your component:

@Component({
  selector: 'admin-menu',
  template: 'admin-menu.html',
  hostDirectives: [{
    directive: MenuBehavior,
    inputs: ['menuId: id'],
    outputs: ['menuClosed: closed'],
  }],
})
export class AdminMenu { }
<admin-menu id="top-menu" (closed)="logMenuClosed()">

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.

In the following example, we define two directives, Menu and Tooltip. We then compose the behavior of these two directives in MenuWithTooltip. Finally, we apply MenuWithTooltip to SpecializedMenuWithTooltip.

When SpecializedMenuWithTooltip is used in a template, it creates instances of all of 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

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',
  template: '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

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.

Getting started with NgOptimizedImage

The NgOptimizedImage directive makes it easy to adopt performance best practices for loading images.

The directive 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 other images by default
  • Automatically generating a preconnect link tag in the document head
  • Automatically generating a srcset attribute
  • Generating a preload hint if app is using SSR

In addition to optimizing the loading of the LCP image, NgOptimizedImage enforces a number of image best practices, such as:

  • Using image CDN URLs to apply image optimizations
  • Preventing layout shift by requiring width and height
  • Warning if width or height have been set incorrectly
  • Warning if the image will be visually distorted when rendered

If you're using a background image in CSS, start here.

NOTE: Although the NgOptimizedImage directive was made a stable feature in Angular version 15, it has been backported and is available as a stable feature in versions 13.4.0 and 14.3.0 as well.

Getting Started

Import `NgOptimizedImage` directive from `@angular/common`:
import { NgOptimizedImage } from '@angular/common'

and include it into the imports array of a standalone component or an NgModule:

imports: [
  NgOptimizedImage,
  // ...
],
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 section at the end of this page. To activate the NgOptimizedImage directive, replace your image's src attribute with ngSrc.

<img ngSrc="cat.jpg">

If you're using a built-in third-party loader, make sure to omit the base URL path from src, as that will be prepended automatically by the loader. Always mark the LCP image on your page as priority to prioritize its loading.

<img ngSrc="cat.jpg" width="400" height="200" priority>

Marking an image as priority applies the following optimizations:

Angular displays a warning during development if the LCP element is an image that does not have the priority attribute. A page’s LCP element can vary based on a number of factors - such as the dimensions of a user's screen, so a page may have multiple images that should be marked priority. See CSS for Web Vitals for more details. In order to prevent image-related layout shifts, NgOptimizedImage requires that you specify a height and width for your image, as follows:

<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.

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

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>

You can use the object-fit CSS property to change how the image will fill its container. If you style your image with object-fit: "contain", the image will maintain its aspect ratio and be "letterboxed" to fit the element. If you set object-fit: "cover", the element will retain its aspect ratio, fully fill the element, and some content may be "cropped" off.

See visual examples of the above at the MDN object-fit documentation.

You can also style your image with the object-position property to adjust its position within its containing element.

IMPORTANT: For the "fill" image to render properly, its parent element must be styled with position: "relative", position: "fixed", or position: "absolute".

How to migrate your background image

Here's a simple step-by-step process for migrating from background-image to NgOptimizedImage. For these steps, we'll refer to the element that has an image background as the "containing element":

  1. Remove the background-image style from the containing element.
  2. Ensure that the containing element has position: "relative", position: "fixed", or position: "absolute".
  3. Create a new image element as a child of the containing element, using ngSrc to enable the NgOptimizedImage directive.
  4. Give that element the fill attribute. Do not include a height and width.
  5. If you believe this image might be your LCP element, add the priority attribute to the image element.

You can adjust how the background image fills the container as described in the Using fill mode section.

Using placeholders

Automatic placeholders

NgOptimizedImage can display an automatic low-resolution placeholder for your image if you're using a CDN or image host that provides automatic image resizing. Take advantage of this feature by adding the placeholder attribute to your image:

<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 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

You can also specify a placeholder using a base64 data URL 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. 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

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}"
/>

Adjusting image styling

Depending on the image's styling, adding width and height attributes may cause the image to render differently. NgOptimizedImage warns you if your image styling renders the image at a distorted aspect ratio.

You can typically fix this by adding height: auto or width: auto to your image styles. For more information, see the web.dev article on the <img> tag.

If the width and height attribute on the image are preventing you from sizing the image the way you want with CSS, consider using fill mode instead, and styling the image's parent element.

Performance Features

NgOptimizedImage includes a number of features designed to improve loading performance in your app. These features are described in this section.

Add resource hints

A preconnect resource hint for your image origin ensures that the LCP image loads as quickly as possible.

Preconnect links are automatically generated for domains provided as an argument to a loader. If an image origin cannot be automatically identified, and no preconnect link is detected for the LCP image, NgOptimizedImage will warn during development. In that case, you should manually add a resource hint to index.html. Within the <head> of the document, add a link tag with rel="preconnect", as shown below:

<link rel="preconnect" href="https://my.cdn.origin" />

To disable preconnect warnings, inject the PRECONNECT_CHECK_BLOCKLIST token:

providers: [
  {provide: PRECONNECT_CHECK_BLOCKLIST, useValue: 'https://your-domain.com'}
],

See more information on automatic preconnect generation here.

Request images at the correct size with automatic srcset

Defining a srcset attribute 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 generates an appropriate srcset for the image, based on the presence and value of the sizes attribute on the image tag.

Fixed-size images

If your image should be "fixed" in size (i.e. the same size across devices, except for pixel density), 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">

Responsive images

If your image should be responsive (i.e. grow and shrink according to viewport size), then you will need to define a sizes attribute to generate the srcset.

If you haven't used sizes before, a good place to start is to set it based on viewport width. For example, if your CSS causes the image to fill 100% of viewport width, set sizes to 100vw and the browser will select the image in the srcset that is closest to the viewport width (after accounting for pixel density). If your image is only likely to take up half the screen (ex: in a sidebar), set sizes to 50vw to ensure the browser selects a smaller image. And so on.

If you find that the above does not cover your desired image behavior, see the documentation on advanced sizes values.

Note that NgOptimizedImage automatically prepends "auto" to the provided sizes value. This is an optimization that increases the accuracy of srcset selection on browsers which support sizes="auto", and is ignored by browsers which do not.

By default, the responsive breakpoints are:

[16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840]

If you would like to customize these breakpoints, you can do so using the IMAGE_CONFIG provider:

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 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. 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

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

By default, 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.

<img ngSrc="cat.jpg" width="400" height="200" loading="eager">

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 or mdn.

Configuring an image loader for NgOptimizedImage

A "loader" is a function that generates an image transformation URL for a given image file. When appropriate, NgOptimizedImage sets the size, format, and image quality transformations for an image.

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.

Based on the image services commonly used with Angular applications, NgOptimizedImage provides loaders preconfigured to work with the following image services:

Image Service Angular API Documentation
Cloudflare Image Resizing provideCloudflareLoader Documentation
Cloudinary provideCloudinaryLoader Documentation
ImageKit provideImageKitLoader Documentation
Imgix provideImgixLoader Documentation
Netlify provideNetlifyLoader Documentation

To use the generic loader no additional code changes are necessary. This is the default behavior.

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

The base URL for your image assets should be passed to the provider factory as an argument. For most sites, this base URL should match one of the following patterns:

You can learn more about the base URL structure in the docs of a corresponding CDN provider.

Custom Loaders

To use a custom loader, provide your loader function as a value for the IMAGE_LOADER DI token. In the example below, the custom loader function returns a URL starting with https://example.com that includes src and width as URL parameters.

providers: [
  {
    provide: IMAGE_LOADER,
    useValue: (config: ImageLoaderConfig) => {
      return `https://example.com/images?src=${config.src}&width=${config.width}`;
    },
  },
],

A loader function for the NgOptimizedImage directive takes an object with the ImageLoaderConfig type (from @angular/common) as its argument and returns the absolute URL of the image asset. The ImageLoaderConfig object contains the src property, and optional width and loaderParams properties.

NOTE: even though the width property may not always be present, a custom loader must use it to support requesting images at various widths in order for ngSrcset to work properly.

The loaderParams Property

There is an additional attribute supported by the NgOptimizedImage directive, called loaderParams, which is specifically designed to support the use of custom loaders. The loaderParams attribute takes an object with any properties as a value, and does not do anything on its own. The data in loaderParams is added to the ImageLoaderConfig object passed to your custom loader, and can be used to control the behavior of the loader.

A common use for loaderParams is controlling advanced image CDN features.

Example custom loader

The following shows an example of a custom loader function. This example function concatenates src and width, 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.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}">

Frequently Asked Questions

Does NgOptimizedImage support the background-image css property?

The NgOptimizedImage does not directly support the background-image css property, but it is designed to easily accommodate the use case of having an image as the background of another element.

For a step-by-step process for migration from background-image to NgOptimizedImage, see the How to migrate your background image section above.

Why can't I use src with NgOptimizedImage?

The ngSrc attribute was chosen as the trigger for NgOptimizedImage due to technical considerations around how images are loaded by the browser. NgOptimizedImage makes programmatic changes to the loading attribute -- if the browser sees the src attribute before those changes are made, it will begin eagerly downloading the image file, and the loading changes will be ignored.

Why is a preconnect element not being generated for my image domain?

Preconnect generation is performed based on static analysis of your application. That means that the image domain must be directly included in the loader parameter, as in the following example:

providers: [
  provideImgixLoader('https://my.base.url/'),
],

If you use a variable to pass the domain string to the loader, or you're not using a loader, the static analysis will not be able to identify the domain, and no preconnect link will be generated. In this case you should manually add a preconnect link to the document head, as described above.

Can I use two different image domains in the same page?

The image loaders provider pattern is designed to be as simple as possible for the common use case of having only a single image CDN used within a component. However, it's still very possible to manage multiple image CDNs using a single provider.

To do this, we recommend writing a custom image loader which uses the loaderParams property to pass a flag that specifies which image CDN should be used, and then invokes the appropriate loader based on that flag.

Can you add a new built-in loader for my preferred CDN?

For maintenance reasons, we don't currently plan to support additional built-in loaders in the Angular repository. Instead, we encourage developers to publish any additional image loaders as third-party packages.

Can I use this with the <picture> tag

No, but this is on our roadmap, so stay tuned.

If you're waiting on this feature, please upvote the Github issue here.

How do I find my LCP image with Chrome DevTools?

  1. Using the performance tab of the Chrome DevTools, click on the "start profiling and reload page" button on the top left. It looks like a page refresh icon.

  2. This will trigger a profiling snapshot of your Angular application.

  3. Once the profiling result is available, select "LCP" in the timings section.

  4. A summary entry should appear in the panel at the bottom. You can find the LCP element in the row for "related node". Clicking on it will reveal the element in the Elements panel.

LCP in the Chrome DevTools

NOTE: This only identifies the LCP element within the viewport of the page you are testing. It is also recommended to use mobile emulation to identify the LCP element for smaller screens. In Angular, you use signals to create and manage state. A signal is a lightweight wrapper around a value.

Use the 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

A 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 signal is read-only; it does not have a set or an update method. Instead, the value of the 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

Use signal and 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 for the full details.

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.

Dependent state with linkedSignal

You can use the 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 function lets you create a signal to hold some state that is intrinsically linked to some other state. Revisiting the example above, linkedSignal can replace 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 works similarly to signal with one key difference— instead of passing a default value, you pass a computation function, just like computed. When the value of the computation changes, the value of the linkedSignal changes to the computation result. This helps ensure that the linkedSignal always has a valid value.

The following example shows how the value of a 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

In some cases, the computation for a linkedSignal needs to account for the previous value of the 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 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, 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 or component input. When the value of source changes, linkedSignal updates its value to 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. 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 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

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 (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,
});

Async reactivity with resources

IMPORTANT: resource is experimental. It's ready for you to try, but it might change before it is stable.

Most signal APIs are synchronous— signal, computed, input, etc. However, applications often need to deal with data that is available asynchronously. A Resource gives you a way to incorporate async data into your application's signal-based code.

You can use a Resource to perform any kind of async operation, but the most common use-case for 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 is the resource function.

import {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
    // - If 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 function accepts a 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.

The loader property defines a 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 below for more details.

Resource has a value signal that contains the results of the loader.

Resource loaders

When creating a resource, you specify a ResourceLoader. This loader is an async function that accepts a single parameter— a ResourceLoaderParams object— and returns a value.

The 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.
abortSignal An AbortSignal. See Aborting requests below for details.

If the params computation returns undefined, the loader function does not run and the resource status becomes 'idle'.

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 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});
  },
});

See AbortSignal on MDN for more details on request cancellation with AbortSignal.

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

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, as described below.

The status signal provides a specific 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' undefined 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 calling of 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 loading indicators and error messages.

Reactive data fetching with httpResource

httpResource is a wrapper around HttpClient that gives you the request status and response as signals. It makes HTTP requests through the Angular HTTP stack, including interceptors.

Understanding dependency injection

Dependency injection, or DI, is one of the fundamental concepts in Angular. DI is wired into the Angular framework and allows classes with Angular decorators, such as Components, Directives, Pipes, and Injectables, to configure dependencies that they need.

Two main roles exist in the DI system: dependency consumer and dependency provider.

Angular facilitates the interaction between dependency consumers and dependency providers using an abstraction called Injector. When a dependency is requested, the injector checks its registry to see if there is an instance already available there. If not, a new instance is created and stored in the registry. Angular creates an application-wide injector (also known as the "root" injector) during the application bootstrap process. In most cases you don't need to manually create injectors, but you should know that there is a layer that connects providers and consumers.

This topic covers basic scenarios of how a class can act as a dependency. Angular also allows you to use functions, objects, primitive types such as string or Boolean, or any other types as dependencies. For more information, see Dependency providers.

Providing a dependency

Consider a class called HeroService that needs to act as a dependency in a component.

The first step is to add the @Injectable decorator to show that the class can be injected.

@Injectable()
class HeroService {}

The next step is to make it available in the DI by providing it. A dependency can be provided in multiple places:

Preferred: At the application root level using providedIn

Providing a service at the application root level using providedIn allows injecting the service into all other classes. Using providedIn enables Angular and JavaScript code optimizers to effectively remove services that are unused (known as tree-shaking).

You can provide a service by using providedIn: 'root' in the @Injectable decorator:

@Injectable({
  providedIn: 'root'
})
class HeroService {}

When you provide the service at the root level, Angular creates a single, shared instance of the HeroService and injects it into any class that asks for it.

At the Component level

You can provide services at @Component level by using the providers field of the @Component decorator. In this case the HeroService becomes available to all instances of this component and other components and directives used in the template.

For example:

@Component({
  selector: 'hero-list',
  template: '...',
  providers: [HeroService]
})
class HeroListComponent {}

When you register a provider at the component level, you get a new instance of the service with each new instance of that component.

NOTE: Declaring a service like this causes HeroService to always be included in your application— even if the service is unused.

At the application root level using ApplicationConfig

You can use the providers field of the ApplicationConfig (passed to the bootstrapApplication function) to provide a service or other Injectable at the application level.

In the example below, the HeroService is available to all components, directives, and pipes:

export const appConfig: ApplicationConfig = {
    providers: [
      { provide: HeroService },
    ]
};

Then, in main.ts:

bootstrapApplication(AppComponent, appConfig)

NOTE: Declaring a service like this causes HeroService to always be included in your application— even if the service is unused.

NgModule based applications

@NgModule-based applications use the providers field of the @NgModule decorator to provide a service or other Injectable available at the application level.

A service provided in a module is available to all declarations of the module, or to any other modules which share the same ModuleInjector. To understand all edge-cases, see Hierarchical injectors.

NOTE: Declaring a service using providers causes the service to be included in your application— even if the service is unused.

Injecting/consuming a dependency

Use Angular's inject function to retrieve dependencies.

import {inject, Component} from 'angular/core';

@Component({/* ... */})
export class UserProfile {
  // You can use the `inject` function in property initializers.
  private userClient = inject(UserClient);

  constructor() {
    // You can also use the `inject` function in a constructor.
    const logger = inject(Logger);
  }
}

You can use the inject function in any injection context. Most of the time, this is in a class property initializer or a class constructor for components, directives, services, and pipes.

When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn't yet exist, the injector creates one using the registered provider, and adds it to the injector before returning the service to Angular.

When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments.

graph TD;
subgraph Injector
serviceA[Service A]
heroService[HeroService]
serviceC[Service C]
serviceD[Service D]
end
direction TB
componentProperty["Component <br> heroService = inject(HeroService)"]
heroService-->componentProperty
style componentProperty text-align: left
Loading

What's next

Creating an injectable service

Service is a broad category encompassing any value, function, or feature that an application needs. A service is typically a class with a narrow, well-defined purpose. A component is one type of class that can use DI.

Angular distinguishes components from services to increase modularity and reusability. By separating a component's view-related features from other kinds of processing, you can make your component classes lean and efficient.

Ideally, a component's job is to enable the user experience and nothing more. A component should present properties and methods for data binding, to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a model).

A component can delegate certain tasks to services, such as fetching data from the server, validating user input, or logging directly to the console. By defining such processing tasks in an injectable service class, you make those tasks available to any component. You can also make your application more adaptable by configuring different providers of the same kind of service, as appropriate in different circumstances.

Angular does not enforce these principles. Angular helps you follow these principles by making it easy to factor your application logic into services and make those services available to components through DI.

Service examples

Here's an example of a service class that logs to the browser console:

export class Logger {
  log(msg: unknown) { console.log(msg); }
  error(msg: unknown) { console.error(msg); }
  warn(msg: unknown) { console.warn(msg); }
}

Services can depend on other services. For example, here's a HeroService that depends on the Logger service, and also uses BackendService to get heroes. That service in turn might depend on the HttpClient service to fetch heroes asynchronously from a server:

import { inject } from "@angular/core";

export class HeroService {
  private heroes: Hero[] = [];

  private backend = inject(BackendService);
  private logger = inject(Logger);

  async getHeroes() {
    // Fetch
    this.heroes = await this.backend.getAll(Hero);
    // Log
    this.logger.log(`Fetched ${this.heroes.length} heroes.`);
    return this.heroes;
  }
}

Creating an injectable service with the CLI

The Angular CLI provides a command to create a new service. In the following example, you add a new service to an existing application.

To generate a new HeroService class in the src/app/heroes folder, follow these steps:

  1. Run this Angular CLI command:
ng generate service heroes/hero

This command creates the following default HeroService:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class HeroService {}

The @Injectable() decorator specifies that Angular can use this class in the DI system. The metadata, providedIn: 'root', means that the HeroService is provided throughout the application.

Add a getHeroes() method that returns the heroes from mock.heroes.ts to get the hero mock data:

import { Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';

@Injectable({
  // declares that this service should be created
  // by the root application injector.
  providedIn: 'root',
})
export class HeroService {
  getHeroes() {
    return HEROES;
  }
}

For clarity and maintainability, it is recommended that you define components and services in separate files.

Injecting services

To inject a service as a dependency into a component, you can declare a class field representing the dependency and use Angular's inject function to initialize it.

The following example specifies the HeroService in the HeroListComponent. The type of heroService is HeroService.

import { inject } from "@angular/core";

export class HeroListComponent {
  private heroService = inject(HeroService);
}

It is also possible to inject a service into a component using the component's constructor:

constructor(private heroService: HeroService)

The inject method can be used in both classes and functions, while the constructor method can naturally only be used in a class constructor. However, in either case a dependency may only be injected in a valid injection context, usually in the construction or initialization of a component.

Injecting services in other services

When a service depends on another service, follow the same pattern as injecting into a component. In the following example, HeroService depends on a Logger service to report its activities:

import { inject, Injectable } from '@angular/core';
import { HEROES } from './mock-heroes';
import { Logger } from '../logger.service';

@Injectable({
  providedIn: 'root',
})
export class HeroService {
  private logger = inject(Logger);

  getHeroes() {
    this.logger.log('Getting heroes.');
    return HEROES;
  }
}

In this example, the getHeroes() method uses the Logger service by logging a message when fetching heroes.

What's next

Configuring dependency providers

The previous sections described how to use class instances as dependencies. Aside from classes, you can also use values such as boolean, string, Date, and objects as dependencies. Angular provides the necessary APIs to make the dependency configuration flexible, so you can make those values available in DI.

Specifying a provider token

If you specify the service class as the provider token, the default behavior is for the injector to instantiate that class using the new operator.

In the following example, the app component provides a Logger instance:

providers: [Logger],

You can, however, configure DI to associate the Logger provider token with a different class or any other value. So when the Logger is injected, the configured value is used instead.

In fact, the class provider syntax is a shorthand expression that expands into a provider configuration, defined by the Provider interface. Angular expands the providers value in this case into a full provider object as follows:

[{ provide: Logger, useClass: Logger }]

The expanded provider configuration is an object literal with two properties:

  • The provide property holds the token that serves as the key for consuming the dependency value.
  • The second property is a provider definition object, which tells the injector how to create the dependency value. The provider-definition can be one of the following:
    • useClass - this option tells Angular DI to instantiate a provided class when a dependency is injected
    • useExisting - allows you to alias a token and reference any existing one.
    • useFactory - allows you to define a function that constructs a dependency.
    • useValue - provides a static value that should be used as a dependency.

The sections below describe how to use the different provider definitions.

Class providers: useClass

The useClass provider key lets you create and return a new instance of the specified class.

You can use this type of provider to substitute an alternative implementation for a common or default class. The alternative implementation can, for example, implement a different strategy, extend the default class, or emulate the behavior of the real class in a test case.

In the following example, BetterLogger would be instantiated when the Logger dependency is requested in a component or any other class:

[{ provide: Logger, useClass: BetterLogger }]

If the alternative class providers have their own dependencies, specify both providers in the providers metadata property of the parent module or component:

[
  UserService, // dependency needed in `EvenBetterLogger`.
  { provide: Logger, useClass: EvenBetterLogger },
]

In this example, EvenBetterLogger displays the user name in the log message. This logger gets the user from an injected UserService instance:

@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}`);
  }
}

Angular DI knows how to construct the UserService dependency, since it has been configured above and is available in the injector.

Alias providers: useExisting

The useExisting provider key lets you map one token to another. In effect, the first token is an alias for the service associated with the second token, creating two ways to access the same service object.

In the following example, the injector injects the singleton instance of NewLogger when the component asks for either the new or the old logger: In this way, OldLogger is an alias for NewLogger.

[
  NewLogger,
  // Alias OldLogger w/ reference to NewLogger
  { provide: OldLogger, useExisting: NewLogger},
]

NOTE: Ensure you do not alias OldLogger to NewLogger with useClass, as this creates two different NewLogger instances.

Factory providers: useFactory

The useFactory provider key lets you create a dependency object by calling a factory function. With this approach, you can create a dynamic value based on information available in the DI and elsewhere in the app.

In the following example, only authorized users should see secret heroes in the HeroService. Authorization can change during the course of a single application session, as when a different user logs in .

To keep security-sensitive information in UserService and out of HeroService, give the HeroService constructor a boolean flag to control display of secret heroes:

class HeroService {
  constructor(
    private logger: Logger,
    private isAuthorized: boolean) { }

  getHeroes() {
    const auth = this.isAuthorized ? 'authorized' : 'unauthorized';
    this.logger.log(`Getting heroes for ${auth} user.`);
    return HEROES.filter(hero => this.isAuthorized || !hero.isSecret);
  }
}

To implement the isAuthorized flag, use a factory provider to create a new logger instance for HeroService. This is necessary as we need to manually pass Logger when constructing the hero service:

const heroServiceFactory = (logger: Logger, userService: UserService) =>
  new HeroService(logger, userService.user.isAuthorized);

The factory function has access to UserService. You inject both Logger and UserService into the factory provider so the injector can pass them along to the factory function:

export const heroServiceProvider = {
  provide: HeroService,
  useFactory: heroServiceFactory,
  deps: [Logger, UserService]
};
  • The useFactory field specifies that the provider is a factory function whose implementation is heroServiceFactory.
  • The deps property is an array of provider tokens. The Logger and UserService classes serve as tokens for their own class providers. The injector resolves these tokens and injects the corresponding services into the matching heroServiceFactory factory function parameters, based on the order specified.

Capturing the factory provider in the exported variable, heroServiceProvider, makes the factory provider reusable.

Value providers: useValue

The useValue key lets you associate a static value with a DI token.

Use this technique to provide runtime configuration constants such as website base addresses and feature flags. You can also use a value provider in a unit test to provide mock data in place of a production data service.

The next section provides more information about the useValue key.

Using an InjectionToken object

Use an InjectionToken object as provider token for non-class dependencies. The following example defines a token, APP_CONFIG. of the type InjectionToken:

import { InjectionToken } from '@angular/core';

export interface AppConfig {
  title: string;
}

export const APP_CONFIG = new InjectionToken<AppConfig>('app.config description');

The optional type parameter, <AppConfig>, and the token description, app.config description, specify the token's purpose.

Next, register the dependency provider in the component using the InjectionToken object of APP_CONFIG:

const MY_APP_CONFIG_VARIABLE: AppConfig = {
  title: 'Hello',
};

providers: [{ provide: APP_CONFIG, useValue: MY_APP_CONFIG_VARIABLE }]

Now, inject the configuration object in the constructor body with the inject function:

export class AppComponent {
  constructor() {
    const config = inject(APP_CONFIG);
    this.title = config.title;
  }
}

Interfaces and DI

Though the TypeScript AppConfig interface supports typing within the class, the AppConfig interface plays no role in DI. In TypeScript, an interface is a design-time artifact, and does not have a runtime representation, or token, that the DI framework can use.

When the TypeScript transpiles to JavaScript, the interface disappears because JavaScript doesn't have interfaces. Because there is no interface for Angular to find at runtime, the interface cannot be a token, nor can you inject it:

// Can't use interface as provider token
[{ provide: AppConfig, useValue: MY_APP_CONFIG_VARIABLE })]
export class AppComponent {
  // Can't inject using the interface as the parameter type
  private config = inject(AppConfig);
}

Injection context

The dependency injection (DI) system relies internally on a runtime context where the current injector is available. This means that injectors can only work when code is executed in such a context.

The injection context is available in these situations:

  • During construction (via the constructor) of a class being instantiated by the DI system, such as an @Injectable or @Component.
  • In the initializer for fields of such classes.
  • In the factory function specified for useFactory of a Provider or an @Injectable.
  • In the factory function specified for an InjectionToken.
  • Within a stack frame that runs in an injection context.

Knowing when you are in an injection context will allow you to use the inject function to inject instances.

Class constructors

Every time the DI system instantiates a class, it does so in an injection context. This is handled by the framework itself. The constructor of the class is executed in that runtime context, which also allows injection of a token using the inject function.

class MyComponent  {
  private service1: Service1;
  private service2: Service2 = inject(Service2); // In context

  constructor() {
    this.service1 = inject(Service1) // In context
  }
}

Stack frame in context

Some APIs are designed to be run in an injection context. This is the case, for example, with router guards. This allows the use of inject within the guard function to access a service.

Here is an example for CanActivateFn

const canActivateTeam: CanActivateFn =
    (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
      return inject(PermissionsService).canActivate(inject(UserToken), route.params.id);
    };

Run within an injection context

When you want to run a given function in an injection context without already being in one, you can do so with runInInjectionContext. This requires access to a given injector, like the EnvironmentInjector, for example:

@Injectable({
  providedIn: 'root',
})
export class HeroService {
  private environmentInjector = inject(EnvironmentInjector);

  someMethod() {
    runInInjectionContext(this.environmentInjector, () => {
      inject(SomeService); // Do what you need with the injected service
    });
  }
}

Note that inject will return an instance only if the injector can resolve the required token.

Asserts the context

Angular provides the assertInInjectionContext helper function to assert that the current context is an injection context.

Using DI outside of a context

Calling inject or calling assertInInjectionContext outside of an injection context will throw error NG0203.

Hierarchical injectors

Injectors in Angular have rules that you can leverage to achieve the desired visibility of injectables in your applications. By understanding these rules, you can determine whether to declare a provider at the application level, in a Component, or in a Directive.

The applications you build with Angular can become quite large, and one way to manage this complexity is to split up the application into a well-defined tree of components.

There can be sections of your page that work in a completely independent way than the rest of the application, with its own local copies of the services and other dependencies that it needs. Some of the services that these sections of the application use might be shared with other parts of the application, or with parent components that are further up in the component tree, while other dependencies are meant to be private.

With hierarchical dependency injection, you can isolate sections of the application and give them their own private dependencies not shared with the rest of the application, or have parent components share certain dependencies with its child components only but not with the rest of the component tree, and so on. Hierarchical dependency injection enables you to share dependencies between different parts of the application only when and if you need to.

Types of injector hierarchies

Angular has two injector hierarchies:

Injector hierarchies Details
EnvironmentInjector hierarchy Configure an EnvironmentInjector in this hierarchy using @Injectable() or providers array in 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() or @Component().
For NgModule based applications, you can provide dependencies with the ModuleInjector hierarchy using an @NgModule() or @Injectable() annotation.

EnvironmentInjector

The EnvironmentInjector can be configured in one of two ways by using:

  • The @Injectable() providedIn property to refer to root or platform
  • The ApplicationConfig providers array Using the @Injectable() providedIn property is preferable to using the ApplicationConfig providers array. With @Injectable() providedIn, 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 is configured by the ApplicationConfig.providers.

Provide services using providedIn of @Injectable() as follows:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'  // <--provides this service in the root EnvironmentInjector
})
export class ItemService {
  name = 'telephone';
}

The @Injectable() decorator identifies a service class. The providedIn property configures a specific EnvironmentInjector, here root, which makes the service available in the root EnvironmentInjector.

ModuleInjector

In the case of NgModule based applications, the ModuleInjector can be configured in one of two ways by using:

  • The @Injectable() providedIn property to refer to root or platform
  • The @NgModule() providers array

ModuleInjector is configured by the @NgModule.providers and NgModule.imports property. ModuleInjector is a flattening of all the providers arrays that can be reached by following the NgModule.imports recursively.

Child ModuleInjector hierarchies are created when lazy loading other @NgModules.

Platform injector

There are two more injectors above root, an additional EnvironmentInjector and NullInjector().

Consider how Angular bootstraps the application with the following in main.ts:

bootstrapApplication(AppComponent, appConfig);

The bootstrapApplication() method creates a child injector of the platform injector which is configured by the ApplicationConfig instance. This is the root EnvironmentInjector.

The platformBrowserDynamic() method creates an injector configured by a PlatformModule, 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() 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() because ultimately, everything ends at the NullInjector() and it returns an error or, in the case of @Optional(), null. For more information on @Optional(), see the @Optional() section of this guide.

The following diagram represents the relationship between the root ModuleInjector and its parent injectors as the previous paragraphs describe.

stateDiagram-v2
    elementInjector: EnvironmentInjector<br>(configured by Angular)<br>has special things like DomSanitizer => providedIn 'platform'
    rootInjector: root EnvironmentInjector<br>(configured by AppConfig)<br>has things for your app => bootstrapApplication(..., AppConfig)
    nullInjector: NullInjector<br>always throws an error unless<br>you use @Optional()

    direction BT
    rootInjector --> elementInjector
    elementInjector --> nullInjector
Loading

While the name root is a special alias, other EnvironmentInjector hierarchies don't have aliases. You have the option to create EnvironmentInjector hierarchies whenever a dynamically loaded component is created, such as with the Router, which will create child EnvironmentInjector hierarchies.

All requests forward up to the root injector, whether you configured it with the ApplicationConfig instance passed to the bootstrapApplication() method, or registered all providers with root in their own services. If you configure an app-wide provider in the ApplicationConfig of bootstrapApplication, it overrides one configured for root in the @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 by listing its provider in the providers list of the ApplicationConfig.

providers: [
  { provide: LocationStrategy, useClass: HashLocationStrategy }
]

For NgModule based applications, configure app-wide providers in the AppModule providers.

ElementInjector

Angular creates ElementInjector hierarchies implicitly for each DOM element.

Providing a service in the @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 section to understand the relationship between the 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 section.

When the component instance is destroyed, so is that service instance.

@Directive() and @Component()

A component is a special type of directive, which means that just as @Directive() has a providers property, @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

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 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 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 based applications, Angular will search the ModuleInjector hierarchy if it cannot find a provider in the ElementInjector hierarchies.

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 configuration when you inject your service.

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 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 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, @NgModule(), or component class, so it isn't available anywhere in the app.

export class OptionalComponent {
  public optional? = inject(OptionalService, {optional: true});
}

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 SelfNoDataComponent, notice the injected LeafService as a property.

@Component({
  selector: 'app-self-no-data',
  templateUrl: './self-no-data.component.html',
  styleUrls: ['./self-no-data.component.css']
})
export class SelfNoDataComponent {
  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 🌷.

### `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 <code>&#x1F33F;</code> value for `emoji`, but you had maple leaf <code>&#x1F341;</code> in the component's `providers` array, Angular would ignore maple leaf <code>&#x1F341;</code> and use fern <code>&#x1F33F;</code>.

To see this in code, assume that the following value for `emoji` is what the parent component were using, as in this service:

<docs-code header="src/app/leaf.service.ts" language="typescript">
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:

@Component({
  selector: 'app-skipself',
  templateUrl: './skipself.component.html',
  styleUrls: ['./skipself.component.css'],
  // Angular would ignore this LeafService instance
  providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }]
})
export class SkipselfComponent {
  // 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

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 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:

@Component({
  selector: 'app-host',
  templateUrl: './host.component.html',
  styleUrls: ['./host.component.css'],
  //  provide the service
  providers: [{ provide: FlowerService, useValue: { emoji: '🌷' } }]
})
export class HostComponent {
  // use host when injecting the service
  flower = inject(FlowerService, {host: true, optional: true});
}

Since HostComponent has the host option , no matter what the parent of HostComponent might have as a flower.emoji value, the HostComponent will use tulip 🌷.

Modifiers with constructor injection

Similarly as presented before, the behavior of constructor injection can be modified with @Optional(), @Self(), @SkipSelf() and @Host().

Import each of them from @angular/core and use each in the component class constructor when you inject your service.

export class SelfNoDataComponent {
  constructor(@Self() @Optional() public leaf?: LeafService) { }
}

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-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()

How you provide services using a @Component() (or @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 children of <parent-component>.

In the logical tree, you will see special attributes: @Provide, @Inject, and @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 Demonstrates that a fallback EnvironmentInjector should be used at this location.

Example app structure

The example application has a FlowerService provided in root with an emoji value of red hibiscus 🌺.

@Injectable({
  providedIn: 'root'
})
export class FlowerService {
  emoji = '🌺';
}

Consider an application with only an AppComponent and a ChildComponent. The most basic rendered view would look like nested HTML elements such as the following:

<app-root> <!-- AppComponent selector -->
  <app-child> <!-- ChildComponent selector -->
  </app-child>
</app-root>

However, behind the scenes, Angular uses a logical view representation as follows when resolving injection requests:

<app-root> <!-- AppComponent selector -->
  <#VIEW>
    <app-child> <!-- ChildComponent 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 AppComponent  {
  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: &#x1F33A;

In the logical tree, this would be represented as follows:

<app-root @ApplicationConfig
        @Inject(FlowerService) flower=>"&#x1F33A;">
  <#VIEW>
    <p>Emoji from FlowerService: {{flower.emoji}} (&#x1F33A;)</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 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. @Components 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 provided by the ApplicationConfig acts as the fallback injector when the injection token can't be found in the ElementInjector hierarchies.

Using the providers array

Now, in the ChildComponent class, add a provider for FlowerService to demonstrate more complex resolution rules in the upcoming sections:

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css'],
  // use the providers array to provide a service
  providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }]
})
export class ChildComponent {
  // inject the service
  flower = inject(FlowerService);
}

Now that the FlowerService is provided in the @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 ChildComponent template.

<p>Emoji from FlowerService: {{flower.emoji}}</p>

To render the new values, add <app-child> to the bottom of the AppComponent template so the view also displays the sunflower:

Child Component
Emoji from FlowerService: &#x1F33B;

In the logical tree, this is represented as follows:

<app-root @ApplicationConfig
        @Inject(FlowerService) flower=>"&#x1F33A;">
  <#VIEW>
    <p>Emoji from FlowerService: {{flower.emoji}} (&#x1F33A;)</p>
    <app-child @Provide(FlowerService="&#x1F33B;")
               @Inject(FlowerService)=>"&#x1F33B;"> <!-- search ends here -->
      <#VIEW> <!-- search starts here -->
        <h2>Child Component</h2>
        <p>Emoji from FlowerService: {{flower.emoji}} (&#x1F33B;)</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()) 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

Use the viewProviders array as another way to provide services in the @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.

For demonstration, we are building an AnimalService to demonstrate viewProviders. First, create an AnimalService with an emoji property of whale 🐳:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AnimalService {
  emoji = '🐳';
}

Following the same pattern as with the FlowerService, inject the AnimalService in the AppComponent class:

export class AppComponent {
    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.component.html',
  styleUrls: ['./child.component.css'],
  // provide services
  providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }],
  viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
})
export class ChildComponent {
  // inject services
  flower = inject(FlowerService); 
  animal = inject(AnimalService)
...
}

Add bindings to the ChildComponent and the AppComponent templates. In the ChildComponent template, add the following binding:

<p>Emoji from AnimalService: {{animal.emoji}}</p>

Additionally, add the same to the AppComponent template:

<p>Emoji from AnimalService: {{animal.emoji}}</p>s

Now you should see both values in the browser:

AppComponent
Emoji from AnimalService: &#x1F433;

Child Component
Emoji from AnimalService: &#x1F436;

The logic tree for this example of viewProviders is as follows:

<app-root @ApplicationConfig
         @Inject(AnimalService) animal=>"&#x1F433;">
  <#VIEW>
    <app-child>
      <#VIEW @Provide(AnimalService="&#x1F436;")
            @Inject(AnimalService=>"&#x1F436;")>
       <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
       <p>Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)</p>
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

Just as with the FlowerService example, the AnimalService is provided in the <app-child> @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

The viewProviders field is conceptually similar to providers, but there is one notable difference. Configured providers in viewProviders are not visible to projected content that ends up as a logical children of the component.

To see the difference between using providers and viewProviders, add another component to the example and call it InspectorComponent. InspectorComponent will be a child of the ChildComponent. In inspector.component.ts, inject the FlowerService and AnimalService during property initialization:

export class InspectorComponent {
  flower = inject(FlowerService);
  animal = inject(AnimalService);
}

You do not need a providers or viewProviders array. Next, in inspector.component.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 InspectorComponent to the ChildComponent imports array.

@Component({
  ...
  imports: [InspectorComponent]
})

Next, add the following to child.component.html:

...

<div class="container">
  <h3>Content projection</h3>
  <ng-content></ng-content>
</div>
<h3>Inside the view</h3>

<app-inspector></app-inspector>

<ng-content> allows you to project content, and <app-inspector> inside the ChildComponent template makes the InspectorComponent a child component of ChildComponent.

Next, add the following to app.component.html to take advantage of content projection.

<app-child>
  <app-inspector></app-inspector>
</app-child>

The browser now renders the following, omitting the previous examples for brevity:

...
Content projection

Emoji from FlowerService: &#x1F33B;
Emoji from AnimalService: &#x1F433;

Emoji from FlowerService: &#x1F33B;
Emoji from AnimalService: &#x1F436;

These four bindings demonstrate the difference between providers and viewProviders. Remember that the dog emoji 🐶 is declared inside the <#VIEW> of ChildComponent and isn't visible to the projected content. Instead, the projected content sees the whale 🐳.

However, in the next output section though, the InspectorComponent is an actual child component of ChildComponent, InspectorComponent 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=>"&#x1F433;">
  <#VIEW>
    <app-child>
      <#VIEW @Provide(AnimalService="&#x1F436;")
            @Inject(AnimalService=>"&#x1F436;")>
        <!-- ^^using viewProviders means AnimalService is available in <#VIEW>-->
        <p>Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)</p>

        <div class="container">
          <h3>Content projection</h3>
          <app-inspector @Inject(AnimalService) animal=>"&#x1F433;">
            <p>Emoji from AnimalService: {{animal.emoji}} (&#x1F433;)</p>
          </app-inspector>
        </div>

        <app-inspector>
          <#VIEW @Inject(AnimalService) animal=>"&#x1F436;">
            <p>Emoji from AnimalService: {{animal.emoji}} (&#x1F436;)</p>
          </#VIEW>
        </app-inspector>
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

The projected content of <app-inspector> sees the whale 🐳, not the dog 🐶, because the dog 🐶 is inside the <app-child> <#VIEW>. The <app-inspector> can only see the dog 🐶 if it is also within the <#VIEW>.

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 the <app-child> as shown in child.component.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: &#x1F33A;

In a logical tree, this same idea might look like this:

<app-root @ApplicationConfig
        @Inject(FlowerService) flower=>"&#x1F33A;">
  <#VIEW>
    <app-child @Provide(FlowerService="&#x1F33B;")>
      <#VIEW @Inject(FlowerService, SkipSelf)=>"&#x1F33A;">
        <!-- 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=>"&#x1F33A;">
  <#VIEW> <!-- end search here with null-->
    <app-child @Provide(FlowerService="&#x1F33B;")> <!-- 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

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: '&#x1F436;' } },
  ],
})

The logical tree looks like this with skipSelf in <app-child>:

<app-root @ApplicationConfig
          @Inject(AnimalService=>"&#x1F433;")>
  <#VIEW><!-- search begins here -->
    <app-child>
      <#VIEW @Provide(AnimalService="&#x1F436;")
             @Inject(AnimalService, SkipSelf=>"&#x1F433;")>
        <!--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

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 ChildComponent 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: '&#x1F436;' } },
  ]
})
export class ChildComponent {
  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=>"&#x1F433;")>
  <#VIEW>
    <app-child>
      <#VIEW @Provide(AnimalService="&#x1F436;")
             inject(AnimalService, {host: true}=>"&#x1F436;")> <!-- host stops search here -->
      </#VIEW>
    </app-child>
  </#VIEW>
</app-root>

Add a viewProviders array with a third animal, hedgehog 🦔, to the app.component.ts @Component() metadata:

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  viewProviders: [
    { provide: AnimalService, useValue: { emoji: '&#x1F994;' } },
  ],
})

Next, add skipSelf along with host to the inject() for the AnimalService injection in child.component.ts. Here are host and skipSelf in the animal property initialization:

export class ChildComponent {
  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 AppComponent viewProviders array, is visible.

The logical tree representation shows why this is:

<app-root @ApplicationConfig
        @Inject(AnimalService=>"&#x1F433;")>
  <#VIEW @Provide(AnimalService="&#x1F994;")
         @Inject(AnimalService, @Optional)=>"&#x1F994;">
    <!-- ^^skipSelf starts here,  host stops here^^ -->
    <app-child>
      <#VIEW @Provide(AnimalService="&#x1F436;")
             inject(AnimalService, {skipSelf:true, host: true, optional: true})=>"&#x1F994;">
               <!-- 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

The ability to configure one or more providers at different levels opens up useful possibilities.

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 VillainsListComponent 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 this service by accident.

Instead, you should provide the VillainsService in the providers metadata of the VillainsListComponent like this:

@Component({
  selector: 'app-villains-list',
  templateUrl: './villains-list.component.html',
  providers: [VillainsService]
})
export class VillainsListComponent {}

By providing VillainsService in the VillainsListComponent metadata and nowhere else, the service becomes available only in the VillainsListComponent and its subcomponent tree.

VillainService is a singleton with respect to VillainsListComponent because that is where it is declared. As long as VillainsListComponent does not get destroyed it will be the same instance of VillainService but if there are multiple instances of VillainsListComponent, then each instance of VillainsListComponent will have its own instance of VillainService.

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 HeroListComponent 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 HeroTaxReturnComponent 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 { Injectable } from '@angular/core';
import { HeroTaxReturn } from './hero';
import { HeroesService } from './heroes.service';

@Injectable()
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 HeroTaxReturnComponent that makes use of HeroTaxReturnService.

import { Component, EventEmitter, 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.component.html',
  styleUrls: [ './hero-tax-return.component.css' ],
  providers: [ HeroTaxReturnService ]
})
export class HeroTaxReturnComponent {
  message = '';

  close = output<void>();

  get taxReturn(): HeroTaxReturn {
    return this.heroTaxReturnService.taxReturn;
  }

  taxReturn = input.required<HeroTaxReturn>();

  constructor() {
    effect(() => {
      this.heroTaxReturnService.taxReturn = this.taxReturn();
    })
  }

  private heroTaxReturnService = inject(HeroTaxReturnService);

  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 input property, which is implemented with getters and setters. The setter initializes the component's own instance of the HeroTaxReturnService with the incoming return. The getter always returns what that 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 HeroTaxReturnComponent to provide the service, using the providers property in the component metadata.

providers: [HeroTaxReturnService]

The HeroTaxReturnComponent 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

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.

graph TD;
subgraph COMPONENT_A[Component A]
subgraph COMPONENT_B[Component B]
COMPONENT_C[Component C]
end
end

style COMPONENT_A fill:#BDD7EE
style COMPONENT_B fill:#FFE699
style COMPONENT_C fill:#A9D18E,color:#000
classDef noShadow filter:none
class COMPONENT_A,COMPONENT_B,COMPONENT_C noShadow
Loading

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).
graph BT;

subgraph A[" "]
direction LR
RootInjector["(A) RootInjector"]
ServicesA["CarService, EngineService, TiresService"]
end

subgraph B[" "]
direction LR
ParentInjector["(B) ParentInjector"]
ServicesB["CarService2, EngineService2"]
end

subgraph C[" "]
direction LR
ChildInjector["(C) ChildInjector"]
ServicesC["CarService3"]
end

direction LR
car["(C) Car"]
engine["(B) Engine"]
tires["(A) Tires"]

direction BT
car-->ChildInjector
ChildInjector-->ParentInjector-->RootInjector

class car,engine,tires,RootInjector,ParentInjector,ChildInjector,ServicesA,ServicesB,ServicesC,A,B,C noShadow
style car fill:#A9D18E,color:#000
style ChildInjector fill:#A9D18E,color:#000
style engine fill:#FFE699,color:#000
style ParentInjector fill:#FFE699,color:#000
style tires fill:#BDD7EE,color:#000
style RootInjector fill:#BDD7EE,color:#000
Loading

More on dependency injection

Optimizing client application size with 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

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() or @ContentChildren() to get <lib-header> and <lib-body>, as in the following:

@Component({
  selector: 'lib-header',,
})
class LibHeaderComponent {}

@Component({
  selector: 'lib-card',,
})
class LibCardComponent {
  @ContentChild(LibHeaderComponent) header: LibHeaderComponent|null = null;
}

Because <lib-header> is optional, the element can appear in the template in its minimal form, <lib-card></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 LibCardComponent actually contains two references to the LibHeaderComponent:

@ContentChild(LibHeaderComponent) header: LibHeaderComponent;
  • One of these reference is in the type position-- that is, it specifies LibHeaderComponent as a type: header: LibHeaderComponent;.
  • The other reference is in the value position-- that is, LibHeaderComponent is the value of the @ContentChild() parameter decorator: @ContentChild(LibHeaderComponent).

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 LibHeaderComponent 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 LibHeaderComponent '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

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.
  • The token is used as a type specifier for constructor injection.

In the following example, both uses of the OtherComponent token cause retention of OtherComponent, preventing it from being tree-shaken when it is not used:

class MyComponent {
  constructor(@Optional() other: OtherComponent) {}

  @ContentChild(OtherComponent) other: OtherComponent|null;
}

Although tokens used only as type specifiers are removed when converted to JavaScript, all tokens used for dependency injection are needed at runtime. These effectively change constructor(@Optional() other: OtherComponent) to constructor(@Optional() @Inject(OtherComponent) other). The token is now in a value position, which causes the tree-shaker to keep the reference.

HELPFUL: Libraries should use tree-shakable providers for all services, providing dependencies at the root level rather than in components or modules.

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 LibHeaderComponent:

abstract class LibHeaderToken {}

@Component({
  selector: 'lib-header',
  providers: [
    {provide: LibHeaderToken, useExisting: LibHeaderComponent}
  ]
  ,
})
class LibHeaderComponent extends LibHeaderToken {}

@Component({
  selector: 'lib-card',,
})
class LibCardComponent {
  @ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;
}

In this example, the LibCardComponent implementation no longer refers to LibHeaderComponent in either the type position or the value position. This lets full tree-shaking of LibHeaderComponent 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, LibHeaderComponent 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() or @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

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 LibCardComponent now queries LibHeaderToken rather than LibHeaderComponent. The following example shows how the pattern lets LibCardComponent communicate with the LibHeaderComponent without actually referring to LibHeaderComponent:

abstract class LibHeaderToken {
  abstract doSomething(): void;
}

@Component({
  selector: 'lib-header',
  providers: [
    {provide: LibHeaderToken, useExisting: LibHeaderComponent}
  ]
  ,
})
class LibHeaderComponent extends LibHeaderToken {
  doSomething(): void {
    // Concrete implementation of `doSomething`
  }
}

@Component({
  selector: 'lib-card',,
})
class LibCardComponent implement AfterContentInit {
  @ContentChild(LibHeaderToken) header: LibHeaderToken|null = null;

  ngAfterContentInit(): void {
    if (this.header !== null) {
      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

Lightweight injection tokens are only useful with components. The Angular style guide suggests that you name components using the "Component" suffix. The example "LibHeaderComponent" 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."

RxJS interop with Angular signals

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

Use the 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 subscribes to the Observable immediately, which may trigger side effects. The subscription created by toSignal automatically unsubscribes from the given Observable when the component or service which calls toSignal is destroyed.

IMPORTANT: toSignal creates a subscription. You should avoid calling it repeatedly for the same Observable, and instead reuse the signal it returns.

Injection context

toSignal by default needs to run in an injection context, such as during construction of a component or service. If an injection context is not available, you can manually specify the Injector to use instead.

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 signals.

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

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

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

When requiredSync is true, 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

By default, 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.

Error and Completion

If an Observable used in toSignal produces an error, that error is thrown when the signal is read.

If an Observable used in toSignal completes, the signal continues to return the most recently emitted value before completion.

Create an RxJS Observable from a signal with toObservable

Use the toObservable utility to create an Observable which tracks the value of a signal. The signal's value is monitored with an 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

toObservable by default needs to run in an injection context, such as during construction of a component or service. If an injection context is not available, you can manually specify the Injector to use instead.

Timing of toObservable

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 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

IMPORTANT: rxResource is experimental. It's ready for you to try, but it might change before it is stable.

Angular's resource function gives you a way to incorporate async data into your application's signal-based code. Building on top of this pattern, 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 accepts a stream function that accepts 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: () => 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 for more details on the parameters passed to the factory function.

In all other ways, rxResource behaves like and provides the same APIs as resource for specifying parameters, reading values, checking loading state, and examining errors.

RxJS interop with component and directive outputs

TIP: This guide assumes you're familiar with component and directive outputs.

The @angular/rxjs-interop package offers two APIs related to component and directive outputs.

Creating an output based on an RxJs Observable

The 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 function has special meaning to the Angular compiler. You may only call 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() directly if you can emit values imperatively.

Creating an RxJS Observable from a component or directive output

The 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 directly if it meets your needs.

Setting up HttpClient

Before you can use HttpClient in your app, you must configure it using dependency injection.

Providing HttpClient through dependency injection

HttpClient is provided using the provideHttpClient helper function, which most apps include in the application providers in app.config.ts.

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
  ]
};

If your app is using NgModule-based bootstrap instead, you can include provideHttpClient in the providers of your app's NgModule:

@NgModule({
  providers: [
    provideHttpClient(),
  ],
  // ... other application configuration
})
export class AppModule {}

You can then inject the HttpClient service as a dependency of your components, services, or other classes:

@Injectable({providedIn: 'root'})
export class ConfigService {
  private http = inject(HttpClient);
  // This service can now make HTTP requests via `this.http`.
}

Configuring features of HttpClient

provideHttpClient accepts a list of optional feature configurations, to enable or configure the behavior of different aspects of the client. This section details the optional features and their usages.

withFetch

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withFetch(),
    ),
  ]
};

By default, HttpClient uses the XMLHttpRequest API to make requests. The withFetch feature switches the client to use the fetch 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.

withInterceptors(...)

withInterceptors configures the set of interceptor functions which will process requests made through HttpClient. See the interceptor guide for more information.

withInterceptorsFromDi()

withInterceptorsFromDi includes the older style of class-based interceptors in the HttpClient configuration. See the interceptor guide for more information.

HELPFUL: Functional interceptors (through withInterceptors) have more predictable ordering and we recommend them over DI-based interceptors.

withRequestsMadeViaParent()

By default, when you configure HttpClient using provideHttpClient within a given injector, this configuration overrides any configuration for HttpClient which may be present in the parent injector.

When you add withRequestsMadeViaParent(), HttpClient is configured to instead pass requests up to the 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 as well.

CRITICAL: You must configure an instance of 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()

Including withJsonpSupport enables the .jsonp() method on HttpClient, which makes a GET request via the JSONP convention for cross-domain loading of data.

HELPFUL: Prefer using CORS to make cross-domain requests instead of JSONP when possible.

withXsrfConfiguration(...)

Including this option allows for customization of HttpClient's built-in XSRF security functionality. See the security guide for more information.

withNoXsrfProtection()

Including this option disables HttpClient's built-in XSRF security functionality. See the security guide for more information.

HttpClientModule-based configuration

Some applications may configure 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() equivalent
HttpClientModule provideHttpClient(withInterceptorsFromDi())
HttpClientJsonpModule withJsonpSupport()
HttpClientXsrfModule.withOptions(...) withXsrfConfiguration(...)
HttpClientXsrfModule.disable() withNoXsrfProtection()
When 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 for multi-injector configurations, as it has more stable behavior. See the withRequestsMadeViaParent feature above.

Making HTTP requests

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 which, when subscribed, sends the request and then emits the results when the server responds.

NOTE: Observables created by 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

Fetching data from a backend often requires making a GET request using the 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() 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 then 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 does not verify that the actual return data matches this type.

Fetching other types of data

By default, HttpClient assumes that servers will return JSON data. When interacting with a non-JSON API, you can tell HttpClient what response type to expect and return 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 containing the raw response bytes
'blob' Blob instance

For example, you can ask 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');
});

Because the value of responseType affects the type returned by 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

Server APIs which perform mutations often require making POST requests with a request body specifying the new state or the change to be made.

The 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 will serialize them accordingly:

body type Serialized as
string Plain text
number, boolean, array, or plain object JSON
ArrayBuffer raw data from the buffer
Blob raw data with the Blob's content type
FormData multipart/form-data encoded data
HttpParams or URLSearchParams application/x-www-form-urlencoded formatted string

IMPORTANT: Remember to .subscribe() to mutation request Observables in order to actually fire the request.

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 if you need more control over the construction or serialization of the parameters.

IMPORTANT: Instances of HttpParams are immutable and cannot be directly changed. Instead, mutation methods such as append() return a new instance of 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 with a custom HttpParameterCodec that determines how HttpClient will encode the parameters into the URL.

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 if you need more control over the construction of headers

IMPORTANT: Instances of HttpHeaders are immutable and cannot be directly changed. Instead, mutation methods such as append() return a new instance of 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

For convenience, 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);
});

Because the value of observe affects the type returned by 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

In addition to the response body or response object, 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 which 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 reportProgress option.

NOTE: The optional fetch implementation of HttpClient does not report upload progress events.

To observe the event stream, set the observe option to 'events':

http.post('/api/upload', myData, {
  reportProgress: true,
  observe: 'events',
}).subscribe(event => {
  switch (event.type) {
    case HttpEventType.UploadProgress:
      console.log('Uploaded ' + event.loaded + ' out of ' + event.total + ' bytes');
      break;
    case HttpEventType.Response:
      console.log('Finished uploading!');
      break;
  }
});

Because the value of observe affects the type returned by 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 reported in the event stream has a type which distinguishes what the event represents:

type value Event meaning
HttpEventType.Sent The request has been dispatched to the server
HttpEventType.UploadProgress An HttpUploadProgressEvent reporting progress on uploading the request body
HttpEventType.ResponseHeader The head of the response has been received, including status and headers
HttpEventType.DownloadProgress An HttpDownloadProgressEvent reporting progress on downloading the response body
HttpEventType.Response The entire response has been received, including the response body
HttpEventType.User A custom event from an Http interceptor.

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 captures all of the above kinds of errors in an HttpErrorResponse which it returns through the Observable's error channel. Network and timeout errors have a status code of 0 and an error which is an instance of ProgressEvent. 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 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

To set a timeout for a request, you can set the timeout option to a number of milliseconds along 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

When using the withFetch() provider, Angular's HttpClient provides access to advanced fetch API options that can improve performance and user experience. These options are only available when using the fetch backend.

Fetch options

The following options provide fine-grained control over request behavior when using the fetch backend.

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

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

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

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: Use mode: 'same-origin' for sensitive requests that should never go cross-origin.

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

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.

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.

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.

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 Observables

Each request method on HttpClient constructs and returns an Observable of the requested response type. Understanding how these Observables work is important when using HttpClient.

HttpClient produces what RxJS calls "cold" Observables, 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 Observables 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, Observables from HttpClient usually complete (although interceptors can influence this).

Because of the automatic completion, there is usually no risk of memory leaks if 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 operation to subscribe to Observables ensures that subscriptions are disposed properly.

Best practices

While 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:

@Injectable({providedIn: 'root'})
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 UserProfileComponent {
  userId = input.required<string>();
  user$!: Observable<User>;

  private userService = inject(UserService);

  constructor(): void {
    effect(() => {
      this.user$ = this.userService.getUser(this.userId());
    });
  }
}

Interceptors

HttpClient supports a form of middleware known as interceptors.

TLDR: Interceptors are middleware that allows common patterns around retrying, caching, logging, and authentication to be abstracted away from individual requests.

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 in their own section at the end.

Interceptors

Interceptors are generally functions which 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 log them.
  • Driving UI elements such as a loading spinner while network operations are in progress.
  • Collecting and batch 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

The basic form of an interceptor is a function which receives the outgoing 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 to use it.

Configuring interceptors

You declare the set of interceptors to use when configuring HttpClient through dependency injection, by using the withInterceptors feature:

bootstrapApplication(AppComponent, {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

An interceptor may transform the Observable stream of HttpEvents 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

Most aspects of HttpRequest and 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 or 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 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

Interceptors are run in the injection context of the injector which registered them, and can use Angular's 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

Often it's useful to include information in a request that's not sent to the backend, but is specifically meant for interceptors. HttpRequests have a .context object which stores this kind of metadata as an instance of HttpContext. This object functions as a typed map, with keys of type 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

To store whether the caching interceptor should cache a particular request in that request's .context map, define a new 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

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

When making a request via the HttpClient API, you can provide values for HttpContextTokens:

const data$ = http.get('/sensitive/data', {
  context: new HttpContext().set(CACHING_ENABLED, false),
});

Interceptors can read these values from the HttpContext of the request.

The request context is mutable

Unlike other properties of HttpRequests, the associated 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

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 constructor:

const resp = new HttpResponse({
  body: 'response body',
});

Working with redirect information

When using HttpClient with the withFetch provider, 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();
      }
    }
  }));
}

DI-based interceptors

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 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(AppComponent, {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.

Test requests

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 in which 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.

At the end, tests can verify that the app made no unexpected requests.

Setup for testing

To begin testing usage of HttpClient, configure TestBed and include provideHttpClient() and provideHttpClientTesting() in your test's setup. This configures HttpClient to use a test backend instead of the real network. It also provides 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 can be injected from TestBed once configured.

Keep in mind to provide provideHttpClient() before provideHttpClientTesting(), as provideHttpClientTesting() will overwrite parts of provideHttpClient(). Doing it the other way around can potentially break your tests.

TestBed.configureTestingModule({
  providers: [
    // ... other test providers
    provideHttpClient(),
    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.

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,
    provideHttpClient(),
    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

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

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

You should test your app's responses when HTTP requests fail.

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

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

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 configuration for this interceptor should rely on the withInterceptors feature.

TestBed.configureTestingModule({
  providers: [
    AuthService,
    // Testing one interceptor at a time is recommended.
    provideHttpClient(withInterceptors([authInterceptor])),
    provideHttpClientTesting(),
  ],
});

The HttpTestingController can retrieve the request instance which can then be inspected to ensure that the request was modified.

const service = TestBed.inject(AuthService);
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 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 },
  ],
});

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, progress to using multiple controls in a group, validate form values, and create dynamic forms where you can add or remove controls at run time.

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 of input values, which 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 the streams have access to manipulate that data safely.

Reactive forms differ from 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 let direct access 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 for detailed comparisons between the two paradigms.

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 that you need to use reactive forms.
  2. Instantiate a new FormControl.
  3. Register the 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.

Use the CLI command `ng generate component` to generate a component in your project and import `ReactiveFormsModule` from the `@angular/forms` package and add it to your Component's `imports` array.
</docs-step>

<docs-step title="Declare a FormControl instance">
Use the constructor of `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.

<docs-code header="src/app/name-editor/name-editor.component.ts" path="adev/src/content/examples/reactive-forms/src/app/name-editor/name-editor.component.ts" visibleRegion="create-control"/>
</docs-step>

<docs-step title="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` binding provided by `FormControlDirective`, which is also included in the `ReactiveFormsModule`.

<docs-code header="src/app/name-editor/name-editor.component.html" path="adev/src/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html" visibleRegion="control-binding" />

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.
</docs-step>

<docs-step title="Display the component">
The `FormControl` assigned to the `name` property is displayed when the `<app-name-editor>` component is added to a template.

<docs-code header="src/app/app.component.html (name editor)" path="adev/src/content/examples/reactive-forms/src/app/app.component.1.html" visibleRegion="app-name-editor"/>
</docs-step>
</docs-workflow>

### 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` 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.

<docs-code header="src/app/name-editor/name-editor.component.html (control value)" path="adev/src/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html" visibleRegion="display-value"/>

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` properties and methods in the [API Reference](api/forms/FormControl 'Detailed syntax reference').

### 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.

<docs-code header="src/app/name-editor/name-editor.component.ts (update value)" path="adev/src/content/examples/reactive-forms/src/app/name-editor/name-editor.component.ts" visibleRegion="update-value"/>

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.

<docs-code header="src/app/name-editor/name-editor.component.html (update value)" path="adev/src/content/examples/reactive-forms/src/app/name-editor/name-editor.component.html" visibleRegion="update-value"/>

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

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` and `FormControl` classes from the `@angular/forms` package.

<docs-code language="shell">
ng generate component ProfileEditor

To add a form group to this component, take the following steps.

  1. Create a FormGroup instance.
  2. Associate the FormGroup model and view.
  3. Save the form data.
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

The individual form controls are now collected within a group. A 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.

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.

Just as a form group contains a group of controls, the profileForm FormGroup is bound to the form element with the FormGroup directive, creating a communication layer between the model and the form containing the inputs. The formControlName input provided by the FormControlName directive binds each individual input to the form control defined in 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.

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` directive listens for the `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.

The onSubmit() method in the ProfileEditor component captures the current value of profileForm. Use EventEmitter 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.

The submit event is emitted by the form tag using the built-in DOM event. You trigger the event by clicking a button with 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.

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 section.

To display the `ProfileEditor` component that contains the form, add it to a component template.

ProfileEditor lets you manage the form control instances for the firstName and lastName controls within the form group instance.

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.

To create a nested group in `profileForm`, add a nested `address` element to the form group instance.

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.

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.

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.

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.

Simulate an update by adding a button to the template to update the user profile on demand.

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

Creating form control instances manually can become repetitive when dealing with multiple forms. The FormBuilder service provides convenient methods for generating controls.

Use the following steps to take advantage of this service.

  1. Import the FormBuilder class.
  2. Inject the 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.

Import the `FormBuilder` class from the `@angular/forms` package. The `FormBuilder` service is an injectable provider from the reactive forms module. Use the `inject()` function to inject this dependency in your component. The `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.

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.

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.

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.

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 class from the @angular/forms package.

In the `ProfileEditor` component, add the `Validators.required` static method as the second item in the array for the `firstName` control. 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.

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.

Creating dynamic forms

FormArray is an alternative to 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 class.
  2. Define a FormArray control.
  3. Access the 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.

Import the `FormArray` class from `@angular/forms` to use for type information. The `FormBuilder` service is ready to create a `FormArray` instance. 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() method to define the array, and the FormBuilder.control() method to populate the array with an initial control.

The aliases control in the form group instance is now populated with a single control until more controls are added dynamically.

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.

Because the returned control is of the type 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() method inserts the control as a new item in the array.

In the template, each control is displayed as a separate input field.

To attach the aliases from your form model, you must add it to the template. Similar to the formGroupName input provided by FormGroupNameDirective, formArrayName binds communication from the form array instance to the template with FormArrayNameDirective.

Add the following template HTML after the <div> closing the formGroupName element.

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 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.

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.

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.

Classes

Class Details
AbstractControl The abstract base class for the concrete form control classes FormControl, FormGroup, and FormArray. It provides their common behaviors and properties.
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 Manages the value and validity state of a group of AbstractControl instances. The group's properties include its child controls. The top-level form in your component is FormGroup.
FormArray Manages the value and validity state of a numerically indexed array of AbstractControl instances.
FormBuilder An injectable service that provides factory methods for creating control instances.
FormRecord Tracks the value and validity state of a collection of FormControl instances, each of which has the same value type.

Directives

Directive Details
FormControlDirective Syncs a standalone FormControl instance to a form control element.
FormControlName Syncs FormControl in an existing FormGroup instance to a form control element by name.
FormGroupDirective Syncs an existing FormGroup instance to a DOM element.
FormGroupName Syncs a nested FormGroup instance to a DOM element.
FormArrayName Syncs a nested FormArray instance to a DOM element.

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.

Overview of Typed Forms

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. For example, you may call login.value, login.controls, login.patchValue, etc. (For a full API reference, see the API documentation.)

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).

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 version. By removing the Untyped prefixes, you can incrementally enable the types.

FormControl: Getting Started

The simplest possible form consists of a single control:

const email = new FormControl('[email protected]');

This control will be automatically inferred to have the type FormControl<string|null>. TypeScript will automatically enforce this type throughout the FormControl API, such as email.value, email.valueChanges, email.setValue(...), etc.

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('[email protected]');
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('[email protected]', {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

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('[email protected]'); // Error!

To prevent this, we explicitly specify the type as string|null:

const email = new FormControl<string|null>(null);
email.setValue('[email protected]');

FormArray: Dynamic, Homogenous Collections

A 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'));

This 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, because TypeScript cannot infer which element type will occur at which position.

FormGroup and FormRecord

Angular provides the FormGroup type for forms with an enumerated set of keys, and a type called FormRecord, for open-ended or dynamic groups.

Partial Values

Consider again a login form:

const login = new FormGroup({
    email: new FormControl('', {nonNullable: true}),
    password: new FormControl('', {nonNullable: true}),
});

On any FormGroup, it is possible to disable controls. 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

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

Some FormGroup usages do not fit the above pattern because the keys are not known ahead of time. The 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.

If you need a 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.

A FormRecord can also be built with the FormBuilder:

const addresses = fb.record({'Andrew': '2340 Folsom St'});

FormBuilder and NonNullableFormBuilder

The 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. 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:

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.

Building a template-driven form

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 to update the data model in the component as changes are made in the template and vice versa. 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 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

This tutorial teaches you how to do the following:

  • Build an Angular form with a component and template
  • Use ngModel 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

Build a template-driven form

Template-driven forms rely on directives defined in the FormsModule.

Directives Details
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 Creates a top-level FormGroup instance and binds it to a <form> element to track aggregated form value and validation status. As soon as you import FormsModule, this directive becomes active by default on all <form> tags. You don't need to add a special selector.
NgModelGroup Creates and binds a FormGroup instance to a DOM element.

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
  2. Bind form controls to data properties using the ngModel directive and two-way data-binding syntax.
    • Examine how ngModel reports control states using CSS classes
    • Name controls to make them accessible to ngModel
  3. Track input validity and control status using ngModel.
    • 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 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

  1. The provided sample application creates the Actor class which defines the data model reflected in the form.
  1. The form layout and details are defined in the ActorFormComponent class.

    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.

  2. The following code creates a new actor instance, so that the initial form can show an example actor.

    This demo uses dummy data for 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.

  3. The component enables the Forms feature by importing the FormsModule module.

  4. The form is displayed in the application layout defined by the root component's template.

    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.

  5. The sample form uses some style classes from Twitter Bootstrap: container, form-group, form-control, and btn. To use these styles, the application's style sheet imports the library.

  1. 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.

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

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 directive declared in the 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 directive, using two-way data binding syntax [(ngModel)]="...".

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

When you imported the FormsModule in your component, Angular automatically created and attached an NgForm directive to the <form> tag in the template (because NgForm has the selector form that matches <form> elements).

To get access to the NgForm and the overall form status, declare a template reference variable.

  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.

    The actorForm template variable is now a reference to the 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

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 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 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:

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.

  • 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

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

Adding the 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

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.

    ;

1. 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

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.
1. Add these class definitions to a new `forms.css` file.
1. Add the new file to the project as a sibling to `index.html`:

<docs-code header="src/assets/forms.css" language="css" path="adev/src/content/examples/forms/src/assets/forms.css"/>

1. In the `index.html` file, update the `<head>` tag to include the new style sheet.

<docs-code header="src/index.html (styles)" path="adev/src/content/examples/forms/src/index.html" visibleRegion="styles"/>

### 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.

<docs-workflow>
<docs-step title="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.
</docs-step>

<docs-step title="Add the error message">
Add a `<div>` that contains a suitable error message.
</docs-step>

<docs-step title="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.
</docs-step>

<docs-code header="src/app/actor-form/actor-form.component.html (hidden-error-msg)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="hidden-error-msg"/>

<docs-step title="Add a conditional error message to name">
Add a conditional error message to the `name` input box, as in the following example.

<docs-code header="src/app/actor-form/actor-form.component.html (excerpt)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="name-with-error-msg"/>
</docs-step>
</docs-workflow>
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

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.
1. In the component file, add the actor-creation method to the actor data model.

<docs-code header="src/app/actor-form/actor-form.component.ts (New Actor method)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.ts" visibleRegion="new-actor"/>

1. Bind the button's click event to an actor-creation method, `newActor()`.

<docs-code header="src/app/actor-form/actor-form.component.html (New Actor button)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="new-actor-button-no-reset"/>

1. 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.

1. 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**.

1. 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.

   <docs-code header="src/app/actor-form/actor-form.component.html (Reset the form)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="new-actor-button-form-reset"/>

   Now clicking **New Actor** resets both the form and its control flags.

## 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.

<docs-workflow>

<docs-step title="Listen to ngOnSubmit">
Bind the form's [`ngSubmit`](api/forms/NgForm#properties) event property to the actor-form component's `onSubmit()` method.

<docs-code header="src/app/actor-form/actor-form.component.html (ngSubmit)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="ngSubmit"/>
</docs-step>

<docs-step title="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.

<docs-code header="src/app/actor-form/actor-form.component.html (submit-button)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="submit-button"/>
</docs-step>

<docs-step title="Run the application">
Notice that the button is enabled —although it doesn't do anything useful yet.
</docs-step>

<docs-step title="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` did this automatically when you defined a template reference variable on the enhanced form element, then referred to that variable in the button control.
</docs-step>
</docs-workflow>

### 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.

<docs-workflow>
<docs-step title="Wrap the form">
Wrap the entire form in a `<div>` and bind its `hidden` property to the `ActorFormComponent.submitted` property.

<docs-code header="src/app/actor-form/actor-form.component.html (excerpt)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="edit-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:

<docs-code header="src/app/actor-form/actor-form.component.ts (submitted)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.ts" visibleRegion="submitted"/>

When you click the **Submit** button, the `submitted` flag becomes true and the form disappears.
</docs-step>

<docs-step title="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.

<docs-code header="src/app/actor-form/actor-form.component.html (excerpt)" path="adev/src/content/examples/forms/src/app/actor-form/actor-form.component.html" visibleRegion="submitted"/>

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.
</docs-step>

<docs-step title="Test the Edit button">
Click the *Edit* button to switch the display back to the editable form.
</docs-step>
</docs-workflow>

## 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` decorator
- Handling form submission by binding to the `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` 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:
# Validating form input

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

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` to a local template variable.
The following example exports `NgModel` into a variable called `name`:

Notice the following features illustrated by the example.

  • The <input> element carries the HTML validation attributes: required and minlength. It also carries a custom validator directive, forbiddenName. For more information, see the Custom validators section.

  • #name="ngModel" exports NgModel into a local variable called name. NgModel mirrors many of the properties of its underlying 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 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, 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

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 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.
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.

For performance reasons, Angular only runs async validators if all sync validators pass. Each must complete before errors are set.

Built-in validator functions

You can choose to write your own validator functions, 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, are all available to use as functions from the Validators class. For a full list of built-in validators, see the 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.

In this example, the name control sets up two built-in validators —Validators.required and Validators.minLength(4)— 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.

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

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.

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

In reactive forms, add a custom validator by passing the function directly to the FormControl.

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 provider, as shown in the following example. NG_VALIDATORS is a predefined provider with an extensible collection of validators.

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.

Once the ForbiddenValidatorDirective is ready, you can add its selector, appForbiddenName, to any input element to activate it. For example:

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

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.

Cross-field validation

A cross-field validator is a custom validator 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

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`.
You query the `FormGroup` for its child controls so that you can compare their values.

To add a validator to the `FormGroup`, pass the new validator in as the second argument on creation.

```javascript
const actorForm = new FormGroup({
  'name': new FormControl(),
  'role': new FormControl(),
  'skill': new FormControl()
}, { validators: unambiguousRoleValidator });

The validator code is as follows.

The `unambiguousRoleValidator` validator implements the `ValidatorFn` interface.
It takes an Angular control object as an argument and returns either null if the form is valid, or `ValidationErrors` otherwise.

The validator retrieves the child controls by calling the `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.

<docs-code header="reactive/actor-form-template.component.html" path="adev/src/content/examples/form-validation/src/app/reactive/actor-form-reactive.component.html" visibleRegion="cross-validation-error-message"/>

This `@if` displays the error if the `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

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.

<docs-code header="shared/unambiguous-role.directive.ts" path="adev/src/content/examples/form-validation/src/app/shared/unambiguous-role.directive.ts" visibleRegion="cross-validation-directive"/>

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.

<docs-code header="template/actor-form-template.component.html" path="adev/src/content/examples/form-validation/src/app/template/actor-form-template.component.html" visibleRegion="cross-validation-register-validator"/>

To provide better user experience, an appropriate error message appears when the form is invalid.

<docs-code header="template/actor-form-template.component.html" path="adev/src/content/examples/form-validation/src/app/template/actor-form-template.component.html" visibleRegion="cross-validation-error-message"/>

This is the same in both template-driven and reactive forms.

## Creating asynchronous validators

Asynchronous validators implement the `AsyncValidatorFn` and `AsyncValidator` interfaces.
These are very similar to their synchronous counterparts, with the following differences.

* The `validate()` functions must return a Promise or an observable,
* The observable returned must be finite, meaning 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.

<docs-code language="html">

<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
@if(model.pending) {
  <app-spinner />
}

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 interface.

The `actorsService` property is initialized with an instance of the `ActorsService` token, which defines the following interface.

<docs-code language="typescript">
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 UnambiguousRoleValidator 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() 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() 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 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 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

To use an async validator in reactive forms, begin by injecting the validator into a property of the component class.

Then, pass the validator function directly to the `FormControl` to apply it.

In the following example, the `validate` function of `UnambiguousRoleValidator` is applied to `roleControl` by passing it to the control's `asyncValidators` option and binding it to the instance of `UnambiguousRoleValidator` 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` options, see the [AbstractControlOptions](api/forms/AbstractControlOptions) API reference.

<docs-code path="adev/src/content/examples/form-validation/src/app/reactive/actor-form-reactive.component.2.ts" visibleRegion="async-validator-usage"/>

### 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` 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` function, triggered by Angular when validation should happen.

<docs-code path="adev/src/content/examples/form-validation/src/app/shared/role.directive.ts" visibleRegion="async-validator-directive"/>

Then, as with synchronous validators, add the directive's selector to an input to activate it.

<docs-code header="template/actor-form-template.component.html (unique-unambiguous-role-input)" path="adev/src/content/examples/form-validation/src/app/template/actor-form-template.component.html" visibleRegion="role-input"/>

### 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` or `blur`.

With template-driven forms, set the property in the template.

<docs-code language="html">
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

With reactive forms, set the property in the FormControl instance.

new FormControl('', {updateOn: 'blur'});

Interaction with native HTML form validation

By default, Angular disables native HTML form 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 for details.

Building 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

Dynamic forms are based on reactive forms.

To give the application access reactive forms directives, import ReactiveFormsModule from the @angular/forms library into the necessary components.

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.

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).

The DropdownQuestion control type presents a list of choices in a select box.

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 instances that consume the metadata from the question model. You can specify default values and validation rules.

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 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. 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 and formGroup selectors. Both directives are defined in ReactiveFormsModule.

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() questions.

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.

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>.

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

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.

Dynamic-Form

Next steps

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?

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

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
  • View transition effects
  • Navigation guards

Next steps

Learn about how you can define routes using Angular router.

Define routes

Routes serve as the fundamental building blocks for navigation within an Angular app.

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/app-admin.component';

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

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/home-page.component';
import { AdminPage } from './about-page/admin-page.component';

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

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 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

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

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. Learn more about query parameters in Angular in this guide.

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 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/user-profile.component';
import { SocialMediaFeed } from './user-profile/social–media-feed.component';

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 for details on reading route parameters.

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.component';
import { UserProfile } from './user-profile/user-profile.component';
import { NotFound } from './not-found/not-found.component';

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

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: HomeComponent },              // Empty path
  { path: 'users/new', component: NewUserComponent },  // Static, most specific
  { path: 'users/:id', component: UserDetailComponent }, // Dynamic
  { path: 'users', component: UsersComponent },        // Static, less specific
  { path: '**', component: NotFoundComponent }         // 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 **

Loading Route Component Strategies

Understanding how and when components load in Angular routing is crucial for building responsive web applications. Angular offers two primary strategies to control component loading behavior:

  1. Eagerly loaded: Components that are loaded immediately
  2. Lazily loaded: Components loaded only when needed

Each approach offers distinct advantages for different scenarios.

Eagerly loaded components

When you define a route with the 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

You can use the loadComponent property to lazily load the JavaScript for a route only at the point at which that route would become active.

import { Routes } from "@angular/router";

export const routes: Routes = [
  // The HomePage and LoginPage components are loaded lazily at the point at which
  // their corresponding routes become active.
  {
    path: 'login',
    loadComponent: () => import('./components/auth/login-page').then(m => m.LoginPage)
  },
  {
    path: '',
    loadComponent: () => import('./components/home/home-page').then(m => m.HomePage)
  }
]

The loadComponent property accepts a loader function that returns a Promise that resolves to an Angular component. In most cases, this function uses the standard JavaScript dynamic import API. You can, however, use any arbitrary async loader function.

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.

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.

Redirects

You can define a route that redirects to another route instead of rendering a component:

import { BlogComponent } from './home/blog.component';

const routes: Routes = [
  {
    path: 'articles',
    redirectTo: '/blog',
  },
  {
    path: 'blog',
    component: BlogComponent
  },
];

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

You can associate a title with each route. Angular automatically updates the page 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 { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ProductsComponent } from './products/products.component';

const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    title: 'Home Page'
  },
  {
    path: 'about',
    component: AboutComponent,
    title: 'About Us'
  },
];

The page title property can be set dynamincally to a resolver function using ResolveFn.

const titleResolver: ResolveFn<string> = (route) => route.queryParams['id'];
const routes: Routes = [
   ...
  {
    path: 'products',
    component: ProductsComponent,
    title: titleResolver,
  }
];

Route titles can also be set via a service extending the TitleStrategy abstract class. By default, Angular uses the DefaultTitleStrategy.

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.

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: AdminUsersComponent},
      {path: 'teams', component: AdminTeamsComponent},
    ],
  },
  // ... 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 for more information about providers and injection in Angular.

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

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 { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ProductsComponent } from './products/products.component';

const routes: Routes = [
  {
    path: 'about',
    component: AboutComponent,
    data: { analyticsId: '456' }
  },
  {
    path: '',
    component: HomeComponent,
    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. See Reading route state for details.

Dynamic data with data resolvers

When you need to provide dynamic data to a route, check out the guide on route data resolvers.

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.

Diagram to illustrate nested routes

You can add child routes to any route definition with the children property:

const routes: Routes = [
  {
    path: 'product/:id',
    component: ProductComponent,
    children: [
      {
        path: 'info',
        component: ProductInfoComponent
      },
      {
        path: 'reviews',
        component: ProductReviewsComponent
      }
    ]
  }
]

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 objects.

To display child routes, the parent component (ProductComponent in the example above) includes its own <router-outlet>.

<!-- ProductComponent -->
<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

Learn how to display the contents of your routes with Outlets.

Show routes with outlets

The RouterOutlet directive is a placeholder that marks the location where the router should render the component for the current URL.

<app-header />
<router-outlet />  <!-- Angular inserts your route content here -->
<app-footer />
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {}

In this example, if an application has the following routes defined:

import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ProductsComponent } from './products/products.component';

const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    title: 'Home Page'
  },
  {
    path: 'products',
    component: ProductsComponent,
    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>...</app-header>
<router-outlet></router-outlet>
<app-admin-page>...</app-admin-page>
<app-footer>...</app-footer>

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',
    component: SettingsComponent, // this is the component with the <router-outlet> in the template
    children: [
      {
        path: 'profile', // child route path
        component: ProfileComponent, // child route component that the router renders
      },
      {
        path: 'security',
        component: SecurityComponent, // 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

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

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 instructs the outlet to attach the subtree
detach When the 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 if you’d like to learn more.

Next steps

Learn how to navigate to routes with Angular Router.

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

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

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 because they are more maintainable across applications because they don’t need to know their absolute position within the routing hierarchy.

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 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

While 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, you can dynamically navigate to routes, pass parameters, and control navigation behavior in your TypeScript code.

router.navigate()

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' }
    });
  }
}

router.navigate() supports both simple and complex routing scenarios, allowing you to pass route parameters, 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 UserDetailComponent {
  private route = inject(ActivatedRoute);
  private router = inject(Router);

  constructor() {}

  // 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 });
  }
}

router.navigateByUrl()

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');

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
});

Next steps

Learn how to read route state to create responsive and context-aware components.

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

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 ProductComponent {
  private activatedRoute = inject(ActivatedRoute);

  constructor() {
    console.log(this.activatedRoute);
  }
}

The 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 strings 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.

Check out the ActivatedRoute API docs for a complete list of what you can access with in the route.

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 UserProfileComponent {
  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,           // https://www.angular.dev
      // 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 and ActivatedRouteSnapshot API docs for a complete list of all properties you can access.

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 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 by prefixing the parameter name with a colon (:).

import { Routes } from '@angular/router';
import { ProductComponent } from './product/product.component';

const routes: Routes = [
  { path: 'product/:id', component: ProductComponent }
];

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 ProductDetailComponent {
  productId = signal('');
  private activatedRoute = inject(ActivatedRoute);

  constructor() {
    // Access route parameters
    this.activatedRoute.params.subscribe((params) => {
      this.productId.set(params['id']);
    });
  }
}

Query Parameters

Query parameters 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 ProductListComponent 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 ProductListComponent implements OnInit {
  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.

Detect active current route with RouterLinkActive

You can use the 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.

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>

When you specify a value for routerLinkActive, you are also defining the same value for ariaCurrentWhenActive. This makes sure that visually impaired users (which may not perceive the different styling being applied) can also identify the active button.

If you want to define a different value for aria, you’ll need to explicitly set the value using the ariaCurrentWhenActive directive.

Route matching strategy

By default, 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

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.

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.

Other common Routing Tasks

This guide covers some other common tasks associated with using Angular router in your application.

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 feature with provideRouter or the bindToComponentInputs option of RouterModule.forRoot.

To get information from a route:

Add the withComponentInputBinding feature to the provideRouter method.

providers: [
  provideRouter(appRoutes, withComponentInputBinding()),
]

Update the component to have an input() property matching the name of the parameter.

id = input.required<string>()
hero = computed(() => this.service.getHero(id));
The router assigns values to all inputs based on the current route when `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.

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: static or resolved route data, path parameters, matrix parameters, and query parameters. If you want to use the parent components route info you will need to set the router paramsInheritanceStrategy option: withRouterConfig({paramsInheritanceStrategy: 'always'})

Displaying a 404 page

To display a 404 page, set up a wildcard route 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: FirstComponent },
  { path: 'second-component', component: SecondComponent },
  { path: '**', component: PageNotFoundComponent },  // 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 PageNotFoundComponent.

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 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>

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 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 CrisisListComponent, 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 AppComponent 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 AppComponent {}

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

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, 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 providers:

Providers Details
PathLocationStrategy The default "HTML5 pushState" style.
HashLocationStrategy The "hash URL" style.

The RouterModule.forRoot() function sets the LocationStrategy to the PathLocationStrategy, which makes it the default strategy. You also have the option of switching to the HashLocationStrategy with an override during the bootstrapping process.

HELPFUL: For more information on providers and the bootstrap process, see Dependency Injection.

Using Angular routes in a single-page application

This tutorial describes how to build a single-page application, SPA that uses multiple Angular routes.

In a Single Page Application (SPA), all of your application's functions exist in a single HTML page. As users access your application's features, the browser needs to render only the parts that matter to the user, instead of loading a new page. This pattern can significantly improve your application's user experience.

To define how users navigate through your application, you use routes. Add routes to define how users navigate from one part of your application to another. You can also configure routes to guard against unexpected or unauthorized behavior.

Objectives

  • Organize a sample application's features into modules.
  • Define how to navigate to a component.
  • Pass information to a component using a parameter.
  • Structure routes by nesting several routes.
  • Check whether users can access a route.
  • Control whether the application can discard unsaved changes.
  • Improve performance by pre-fetching route data and lazy loading feature modules.
  • Require specific criteria to load components.

Create a sample application

Using the Angular CLI, create a new application, angular-router-sample. This application will have two components: crisis-list and heroes-list.

  1. Create a new Angular project, angular-router-sample.

ng new angular-router-sample

    When prompted with `Would you like to add Angular routing?`, select `N`.

    When prompted with `Which stylesheet format would you like to use?`, select `CSS`.

    After a few moments, a new project, `angular-router-sample`, is ready.

1. From your terminal, navigate to the `angular-router-sample` directory.
1. Create a component, *crisis-list*.

    ```shell
ng generate component crisis-list
  1. In your code editor, locate the file, crisis-list.component.html and replace the placeholder content with the following HTML.

  2. Create a second component, heroes-list.

    ng generate component heroes-list
1. In your code editor, locate the file, `heroes-list.component.html` and replace the placeholder content with the following HTML.

    ```
1. In your code editor, open the file, `app.component.html` and replace its contents with the following HTML.

    <docs-code header="src/app/app.component.html" path="adev/src/content/examples/router-tutorial/src/app/app.component.html" visibleRegion="setup"/>

1. Verify that your new application runs as expected by running the `ng serve` command.

    <docs-code language="shell">
    ng serve
  1. Open a browser to http://localhost:4200.

    You should see a single web page, consisting of a title and the HTML of your two components.

Define your routes

In this section, you'll define two routes:

  • The route /crisis-center opens the crisis-center component.
  • The route /heroes-list opens the heroes-list component.

A route definition is a JavaScript object. Each route typically has two properties. The first property, path, is a string that specifies the URL path for the route. The second property, component, is a string that specifies what component your application should display for that path.

  1. From your code editor, create and open the app.routes.ts file.

  2. Create and export a routes list for your application:

import {Routes} from '@angular/router';

export const routes = [];
```
  1. Add two routes for your first two components:

{path: 'crisis-list', component: CrisisListComponent}, {path: 'heroes-list', component: HeroesListComponent}, ```

This routes list is an array of JavaScript objects, with each object defining the properties of a route.

Import provideRouter from @angular/router

Routing lets you display specific views of your application depending on the URL path. To add this functionality to your sample application, you need to update the app.config.ts file to use the router providers function, provideRouter. You import this provider function from @angular/router.

  1. From your code editor, open the app.config.ts file.

  2. Add the following import statements:

import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; ```

  1. Update the providers in the appConfig:

providers: [provideRouter(routes)] ```

For NgModule based applications, put the provideRouter in the providers list of the AppModule, or whichever module is passed to bootstrapModule in the application.

Update your component with router-outlet

At this point, you have defined two routes for your application. However, your application still has both the crisis-list and heroes-list components hard-coded in your app.component.html template. For your routes to work, you need to update your template to dynamically load a component based on the URL path.

To implement this functionality, you add the router-outlet directive to your template file.

  1. From your code editor, open the app.component.html file.

  2. Delete the following lines.

  3. Add the router-outlet directive.

  4. Add RouterOutlet to the imports of the AppComponent in app.component.ts

imports: [RouterOutlet], ```

View your updated application in your browser. You should see only the application title. To view the crisis-list component, add crisis-list to the end of the path in your browser's address bar. For example:

http://localhost:4200/crisis-list ``` Notice that the `crisis-list` component displays. Angular is using the route you defined to dynamically load the component. You can load the `heroes-list` component the same way:
http://localhost:4200/heroes-list

Control navigation with UI elements

Currently, your application supports two routes. However, the only way to use those routes is for the user to manually type the path in the browser's address bar. In this section, you'll add two links that users can click to navigate between the heroes-list and crisis-list components. You'll also add some CSS styles. While these styles are not required, they make it easier to identify the link for the currently-displayed component. You'll add that functionality in the next section.

  1. Open the app.component.html file and add the following HTML below the title.

This HTML uses an Angular directive, routerLink. This directive connects the routes you defined to your template files.

  1. Add the RouterLink directive to the imports list of AppComponent in app.component.ts.

  2. Open the app.component.css file and add the following styles.

If you view your application in the browser, you should see these two links. When you click on a link, the corresponding component appears.

Identify the active route

While users can navigate your application using the links you added in the previous section, they don't have a straightforward way to identify what the active route is. Add this functionality using Angular's routerLinkActive directive.

  1. From your code editor, open the app.component.html file.

  2. Update the anchor tags to include the routerLinkActive directive.

  3. Add the RouterLinkActive directive to the imports list of AppComponent in app.component.ts.

View your application again. As you click one of the buttons, the style for that button updates automatically, identifying the active component to the user. By adding the routerLinkActive directive, you inform your application to apply a specific CSS class to the active route. In this tutorial, that CSS class is activebutton, but you could use any class that you want.

Note that we are also specifying a value for the routerLinkActive's ariaCurrentWhenActive. This makes sure that visually impaired users (which may not perceive the different styling being applied) can also identify the active button. For more information see the Accessibility Best Practices Active links identification section.

Adding a redirect

In this step of the tutorial, you add a route that redirects the user to display the /heroes-list component.

  1. From your code editor, open the app.routes.ts file.

  2. Update the routes section as follows.

{path: '', redirectTo: '/heroes-list', pathMatch: 'full'}, ```

Notice that this new route uses an empty string as its path.
In addition, it replaces the `component` property with two new ones:

| Properties   | Details |
|:---        |:---    |
| `redirectTo` | This property instructs Angular to redirect from an empty path to the `heroes-list` path.                                                                                                                                                       |
| `pathMatch`  | This property instructs Angular on how much of the URL to match. For this tutorial, you should set this property to `full`. This strategy is recommended when you have an empty string for a path. For more information about this property, see the [Route API documentation](api/router/Route). |

Now when you open your application, it displays the heroes-list component by default.

Adding a 404 page

It is possible for a user to try to access a route that you have not defined. To account for this behavior, the best practice is to display a 404 page. In this section, you'll create a 404 page and update your route configuration to show that page for any unspecified routes.

  1. From the terminal, create a new component, PageNotFound.

    ng generate component page-not-found
1. From your code editor, open the `page-not-found.component.html` file and replace its contents with the following HTML.

    <docs-code header="src/app/page-not-found/page-not-found.component.html" path="adev/src/content/examples/router-tutorial/src/app/page-not-found/page-not-found.component.html"/>

1. Open the `app.routes.ts` file and add the following route to the routes list:

    ```ts
{path: '**', component: PageNotFoundComponent}
    ```

    The new route uses a path, `**`.
    This path is how Angular identifies a wildcard route.
    Any route that does not match an existing route in your configuration will use this route.

IMPORTANT: Notice that the wildcard route is placed at the end of the array.
The order of your routes is important, as Angular applies routes in order and uses the first match it finds.

Try navigating to a non-existing route on your application, such as `http://localhost:4200/powers`.
This route doesn't match anything defined in your `app.routes.ts` file.
However, because you defined a wildcard route, the application automatically displays your `PageNotFound` component.

## Next steps

At this point, you have a basic application that uses Angular's routing feature to change what components the user can see based on the URL address.
You have extended these features to include a redirect, as well as a wildcard route to display a custom 404 page.

For more information about routing, see the following topics:
# Creating custom route matches

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`.
This matcher looks for a Twitter handle in the URL.

## Objectives

Implement Angular's `UrlMatcher` to create a custom route matcher.

## 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*.

    ```shell
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.

1. From your terminal, navigate to the `angular-custom-route-match` directory.
1. Create a component, *profile*.

    ```shell
ng generate component profile
    ```

1. In your code editor, locate the file, `profile.component.html` and replace the placeholder content with the following HTML.

    ```
1. In your code editor, locate the file, `app.component.html` and replace the placeholder content with the following HTML.

    <docs-code header="src/app/app.component.html" path="adev/src/content/examples/routing-with-urlmatcher/src/app/app.component.html"/>

## 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 a Twitter handle in the URL.
This handle is identified by a preceding `@` symbol.

1. In your code editor, open your `app.config.ts` file.
1. Add an `import` statement for Angular's `provideRouter` and `withComponentInputBinding` as well as the application routes.

    ```ts
import {provideRouter, withComponentInputBinding} from '@angular/router';

    import {routes} from './app.routes';
    ```

1. In the providers array, add a `provideRouter(routes, withComponentInputBinding())` statement.

1. Define the custom route matcher by adding the following code to the application routes.

    <docs-code header="src/app/app.routes.ts" path="adev/src/content/examples/routing-with-urlmatcher/src/app/app.routes.ts" visibleRegion="matcher"/>

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

With the custom matcher in place, you can now bind the route parameter in the `profile` component.

In your code editor, open your `profile.component.ts` file and create an `Input` matching the `username` parameter.
We added the `withComponentInputBinding` feature earlier
in `provideRouter`. This allows the `Router` to bind information directly to the route components.

```ts
@Input() username!: string;

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
1. Open a browser to `http://localhost:4200`.

    You should see a single web page, consisting of a sentence that reads `Navigate to my profile`.

1. Click the **my profile** hyperlink.

    A new sentence, reading `Hello, Angular!` appears on the page.

## 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://twitter.com/brandontroberts).
# Server and hybrid rendering

Angular ships all applications as client-side rendered (CSR) by default. While this approach delivers a 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?

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

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:

```shell
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. For more details, see Server routing and Generate a fully static application.

Server routing

Configuring server routes

You can create a server route config by declaring an array of ServerRoute 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 using the withRoutes function:

import { provideServerRendering, withRoutes } from '@angular/ssr';
import { serverRoutes } from './app.routes.server';

// app.config.server.ts
const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(withRoutes(serverRoutes)),
    // ... other providers ...
  ]
};

When using the App shell pattern, you must specify the component to be used as the app shell for client-side rendered routes. To do this, use the withAppShell feature:

import { provideServerRendering, withRoutes, withAppShell } from '@angular/ssr';
import { AppShellComponent } from './app-shell/app-shell.component';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(
      withRoutes(serverRoutes),
      withAppShell(AppShellComponent),
    ),
    // ... other providers ...
  ]
};

Rendering modes

The server routing configuration lets you specify how each route in your application should render by setting a RenderMode:

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.

Choosing a rendering mode

Each rendering mode has different benefits and drawbacks. You can choose rendering modes based on the specific needs of your application.

Client-side rendering (CSR)

Client-side rendering has the simplest development model, as you can write code that assumes it always runs in a web browser. This lets you use a wide range of client-side libraries that also assume they run in a browser.

Client-side rendering generally has worse performance than other rendering modes, as it must download, parse, and execute your page's JavaScript before the user can see any rendered content. If your page fetches more data from the server as it renders, users also have to wait for those additional requests before they can view the complete content.

If your page is indexed by search crawlers, client-side rendering may negatively affect search engine optimization (SEO), as search crawlers have limits to how much JavaScript they execute when indexing a page.

When client-side rendering, the server does not need to do any work to render a page beyond serving static JavaScript assets. You may consider this factor if server cost is a concern.

Applications that support installable, offline experiences with service workers can rely on client-side rendering without needing to communicate with a server.

Server-side rendering (SSR)

Server-side rendering offers faster page loads than client-side rendering. Instead of waiting for JavaScript to download and run, the server directly renders an HTML document upon receiving a request from the browser. The user experiences only the latency necessary for the server to fetch data and render the requested page. This mode also eliminates the need for additional network requests from the browser, as your code can fetch data during rendering on the server.

Server-side rendering generally has excellent search engine optimization (SEO), as search crawlers receive a fully rendered HTML document.

Server-side rendering requires you to author code that does not strictly depend on browser APIs and limits your selection of JavaScript libraries that assume they run in a browser.

When server-side rendering, your server runs Angular to produce an HTML response for every request which may increase server hosting costs.

Build-time prerendering

Prerendering offers faster page loads than both client-side rendering and server-side rendering. Because prerendering creates HTML documents at build-time, the server can directly respond to requests with the static HTML document without any additional work.

Prerendering requires that all information necessary to render a page is available at build-time. This means that prerendered pages cannot include any data to the specific user loading the page. Prerendering is primarily useful for pages that are the same for all users of your application.

Because prerendering occurs at build-time, it may add significant time to your production builds. Using getPrerenderParams to produce a large number of HTML documents may affect the total file size of your deployments, and thus lead to slower deployments.

Prerendering generally has excellent search engine optimization (SEO), as search crawlers receive a fully rendered HTML document.

Prerendering requires you to author code that does not strictly depend on browser APIs and limits your selection of JavaScript libraries that assume they run in a browser.

Prerendering incurs extremely little overhead per server request, as your server responds with static HTML documents. Static files are also easily cached by Content Delivery Networks (CDNs), browsers, and intermediate caching layers for even faster subsequent page loads. Fully static sites can also be deployed solely through a CDN or static file server, eliminating the need to maintain a custom server runtime for your application. This enhances scalability by offloading work from an application web server, making it particularly beneficial for high-traffic applications.

NOTE: When using Angular service worker, the first request is server-rendered, but all subsequent requests are handled by the service worker and rendered client-side.

Setting headers and status codes

You can set custom headers and status codes for individual server routes using the headers and status properties in the ServerRoute configuration.

// app.routes.server.ts
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
];

Redirects

Angular handles redirects specified by the redirectTo property in route configurations, differently on the server-side.

Server-Side Rendering (SSR) Redirects are performed using standard HTTP redirects (e.g., 301, 302) within the server-side rendering process.

Prerendering (SSG) Redirects are implemented as "soft redirects" using <meta http-equiv="refresh"> tags in the prerendered HTML.

Customizing build-time prerendering (SSG)

When using RenderMode.Prerender, you can specify several configuration options to customize the prerendering and serving process.

Parameterized routes

For each route with RenderMode.Prerender, you can specify a getPrerenderParams function. This function lets you control which specific parameters produce separate prerendered documents.

The getPrerenderParams function returns a Promise that resolves to an array of objects. Each object is a key-value map of route parameter name to value. For example, if you define a route like post/:id, getPrerenderParams could return the array [{id: 123}, {id: 456}], and thus render separate documents for post/123 and post/456.

The body of getPrerenderParams can use Angular's inject function to inject dependencies and perform any work to determine which routes to prerender. This typically includes making requests to fetch data to construct the array of parameter values.

You can also use this function with 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 can be combined with other parameters (e.g., /post/:id/**) to handle more complex route configuration.

// app.routes.server.ts
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
    },
  },
];

Because getPrerenderParams exclusively applies to RenderMode.Prerender, this function always runs at build-time. getPrerenderParams must not rely on any browser-specific or server-specific APIs for data.

IMPORTANT: When using inject inside getPrerenderParams, please remember that inject must be used synchronously. It cannot be invoked within asynchronous callbacks or following any await statements. For more information, refer to runInInjectionContext.

Fallback strategies

When using RenderMode.Prerender mode, you can specify a fallback strategy to handle requests for paths that haven't been prerendered.

The available fallback strategies are:

  • Server: Falls back to server-side rendering. This is the default behavior if no fallback property is specified.
  • Client: Falls back to client-side rendering.
  • None: No fallback. Angular will not handle requests for paths that are not prerendered.
// app.routes.server.ts
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

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 and afterNextRender lifecycle hooks. These are only executed on the browser and skipped on the server.

import { Component, ViewChild, afterNextRender } from '@angular/core';

@Component({
  selector: 'my-cmp',
  template: `<span #content>{{ ... }}</span>`,
})
export class MyComponent {
  @ViewChild('content') contentRef: ElementRef;

  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);
    });
  }
}

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: Provides access to the current request object, which is of type Request from the Web API. This allows you to access headers, cookies, and other request information.
  • RESPONSE_INIT: Provides access to the response initialization options, which is of type ResponseInit 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: Provides access to additional context related to the current request. This context can be passed as the second parameter of the handle 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);
  }
}

IMPORTANT: The above tokens will be null in the following scenarios:

  • During the build processes.
  • When the application is rendered in the browser (CSR).
  • When performing static site generation (SSG).
  • During route extraction in development (at the time of the request).

Generate a fully static application

By default, Angular prerenders your entire application and generates a server file for handling requests. This allows your app to serve pre-rendered content to users. However, if you prefer a fully static site without a server, you can opt out of this behavior by setting the outputMode to static in your angular.json configuration file.

When outputMode is set to static, Angular generates pre-rendered HTML files for each route at build time, but it does not generate a server file or require a Node.js server to serve the app. This is useful for deploying to static hosting providers where a backend server is not needed.

To configure this, update your angular.json file as follows:

{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "options": {
            "outputMode": "static"
          }
        }
      }
    }
  }
}

Caching data when using HttpClient

HttpClient cached 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 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 stops using the cache once an application becomes stable while running in a browser.

By default, HttpClient caches all HEAD and GET requests which don't contain Authorization or Proxy-Authorization headers. You can override those settings by using withHttpTransferCacheOptions when providing hydration.

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(withHttpTransferCacheOptions({
      includePostRequests: true
    }))
  ]
});

Configuring a server

Node.js

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 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

The @angular/ssr provides essential APIs for server-side rendering your Angular application on platforms other than Node.js. It leverages the standard Request and 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.

// 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.render(req);

  // ...
});

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?

Hydration improves application performance by avoiding extra work to re-create DOM nodes. Instead, Angular tries to match existing DOM elements to the applications structure at runtime and reuses DOM nodes when possible. This results in a performance improvement that can be measured using Core Web Vitals (CWV) statistics, such as reducing the First Input Delay (FID) and Largest Contentful Paint (LCP), as well as Cumulative Layout Shift (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 like 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

Hydration can be enabled for server-side rendered (SSR) applications only. Follow the Angular SSR Guide to enable server-side rendering first.

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

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 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(AppComponent, {
  providers: [provideClientHydration()]
});

Alternatively if you are using NgModules, you would add provideClientHydration to your root app module's provider list.

import {provideClientHydration} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

@NgModule({
  declarations: [AppComponent],
  exports: [AppComponent],
  bootstrap: [AppComponent],
  providers: [provideClientHydration()],
})
export class AppModule {}

IMPORTANT: Make sure that the 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() call to the providers list in the server bootstrap configuration.

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, Direct DOM Manipulation, and 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 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

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. Starting from v18, you can enable the Event Replay feature that allows to capture all events that happen before hydration and replay those events once hydration has completed. You can enable it using the withEventReplay() function, for example:

import {provideClientHydration, withEventReplay} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [
    provideClientHydration(withEventReplay())
  ]
});

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.


This feature ensures a consistent user experience, preventing user actions performed before Hydration from being ignored. NOTE: if you have incremental hydration enabled, event replay is automatically enabled under the hood.

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

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).

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) until you can refactor into a hydration friendly solution.

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 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

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

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

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 you may encounter this type of error was mentioned in the Valid HTML structure section earlier. So, make sure the HTML in your templates are using valid structure, and you'll avoid that error case.

For a full reference on hydration related errors, visit the Errors Reference Guide.

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. 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

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 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.

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 to your provideClientHydration call.

import {
  bootstrapApplication,
  provideClientHydration,
  withI18nSupport,
} from '@angular/platform-browser';
...

bootstrapApplication(AppComponent, {
  providers: [provideClientHydration(withI18nSupport())]
});

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 function. These rendering differences cause layout shifts, negatively impacting end-user experience and core web vitals.

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

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 to delay the script until post-hydration processes have occurred.

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 for more information.

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.

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) and Cumulative Layout Shift (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?

You can enable incremental hydration for applications that already use server-side rendering (SSR) with hydration. Follow the Angular SSR Guide to enable server-side rendering and the Angular Hydration Guide to enable hydration first.

Enable incremental hydration by adding the withIncrementalHydration() function to the provideClientHydration provider.

import {
  bootstrapApplication,
  provideClientHydration,
  withIncrementalHydration,
} from '@angular/platform-browser';
...

bootstrapApplication(AppComponent, {
  providers: [provideClientHydration(withIncrementalHydration())]
});

Incremental Hydration depends on and enables event replay automatically. If you already have withEventReplay() in your list, you can safely remove it after enabling incremental hydration.

How does incremental hydration work?

Incremental hydration builds on top of full-application hydration, deferrable views, and event replay. 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

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 specifies a condition for when hydration is triggered for the @defer block.

The available triggers are as follows:

Trigger Description
hydrate on idle Triggers when the browser is idle.
hydrate on viewport Triggers when specified content enters the viewport
hydrate on interaction Triggers when the user interacts with specified element
hydrate on hover Triggers when the mouse hovers over specified area
hydrate on immediate Triggers immediately after non-deferred content has finished rendering
hydrate on timer Triggers after a specific duration

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.

@defer (hydrate on idle) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

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.

@defer (hydrate on viewport) {
  <large-cmp />
} @placeholder {
  <div>Large component placeholder</div>
}

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

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

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

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

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

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 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?

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

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 section for more details.

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.

Testing

Testing your Angular application helps you check that your application is working as you expect.

Set up testing

The Angular CLI downloads and installs everything you need to test an Angular application with Jasmine testing framework.

The project you create with the CLI is immediately ready to test. Just run the ng test CLI command:

ng test

The ng test command builds the application in watch mode, and launches the Karma test runner.

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 last line of the log shows that Karma ran three tests that all passed.

The test output is displayed in the browser using Karma Jasmine HTML Reporter.

Jasmine HTML Reporter in the browser

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 app.component.ts and save. The tests run again, the browser refreshes, and the new test results appear.

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.

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.

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 distinctive installation procedures, configuration, and syntax.

Test file name and location

Inside the src/app folder the Angular CLI generated a test file for the AppComponent named app.component.spec.ts.

IMPORTANT: The test file extension must be .spec.ts so that tooling can identify it as a file with tests (also known as a spec file).

The app.component.ts and app.component.spec.ts files are siblings in the same folder. The root file names (app.component) are the same for both files.

Adopt these two conventions in your own projects for every kind of test file.

Place your spec file next to the file it tests

It's a good idea to put unit test spec files in the same folder as the application source code files that they test:

  • Such tests are painless to find
  • You see at a glance if a part of your application lacks tests
  • Nearby tests can reveal how a part works in context
  • When you move the source (inevitable), you remember to move the test
  • When you rename the source file (inevitable), you remember to rename the test file

Place your spec files in a test folder

Application integration specs can test the interactions of multiple parts spread across folders and modules. They don't really belong to any part in particular, so they don't have a natural home next to any one file.

It's often better to create an appropriate folder for them in the tests directory.

Of course specs that test the test helpers belong in the test folder, next to their corresponding helper files.

Testing in continuous integration

One of the best ways to keep your project bug-free is through a test suite, but you might forget to run tests all the time.

Continuous integration (CI) servers let you set up your project repository so that your tests run on every commit and pull request.

To test your Angular CLI application in Continuous integration (CI) run the following command:

ng test --no-watch --no-progress --browsers=ChromeHeadless

More information on testing

After you've set up your application for testing, you might find the following testing guides useful.

Details
Code coverage How much of your app your tests are covering and how to specify required amounts.
Testing services How to test the services your application uses.
Basics of testing components Basics of testing Angular components.
Component testing scenarios Various kinds of component testing scenarios and use cases.
Testing attribute directives How to test your attribute directives.
Testing pipes How to test pipes.
Debugging tests Common testing bugs.
Testing utility APIs Angular testing features.

Find out how much code you're testing

The Angular CLI can run unit tests and create code coverage reports. Code coverage reports show you any parts of your code base that might not be properly tested by your unit tests.

To generate a coverage report run the following command in the root of your project.

ng test --no-watch --code-coverage

When the tests are complete, 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, set the following option in the Angular CLI configuration file, angular.json:

"test": {
  "options": {
    "codeCoverage": true
  }
}

Code coverage enforcement

The code coverage percentages let you estimate how much of your code is tested. If your team decides on a set minimum amount to be unit tested, enforce this minimum with the Angular CLI.

For example, suppose you want the code base to have a minimum of 80% code coverage. To enable this, open the Karma test platform configuration file, karma.conf.js, and add the check property in the coverageReporter: key.

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
    }
  }
}

HELPFUL: Read more about creating and fine tuning Karma configuration in the testing guide.

The check property causes the tool to enforce a minimum of 80% code coverage when the unit tests are run in the project.

Read more on coverage configuration options in the karma coverage documentation.

Testing services

To check that your services are working as you intend, you can write tests specifically for them.

Services are often the smoothest files to unit test. Here are some synchronous and asynchronous unit tests of the ValueService written without assistance from Angular testing utilities.

## Services with dependencies

Services often depend on other services that Angular injects into the constructor.
In many cases, you can create and *inject* these dependencies by hand while calling the service's constructor.

The `MasterService` is a simple example:

<docs-code header="app/demo/demo.ts" path="adev/src/content/examples/testing/src/app/demo/demo.ts" visibleRegion="MasterService"/>

`MasterService` delegates its only method, `getValue`, to the injected `ValueService`.

Here are several ways to test it.

<docs-code header="app/demo/demo.spec.ts" path="adev/src/content/examples/testing/src/app/demo/demo.spec.ts" visibleRegion="MasterService"/>

The first test creates a `ValueService` with `new` and passes it to the `MasterService` constructor.

However, injecting the real service rarely works well as most dependent services are difficult to create and control.

Instead, mock the dependency, use a dummy value, or create a [spy](https://jasmine.github.io/tutorials/your_first_suite#section-Spies) on the pertinent service method.

HELPFUL: Prefer spies as they are usually the best way to mock services.

These standard testing techniques are great for unit testing services in isolation.

However, you almost always inject services into application classes using Angular dependency injection and you should have tests that reflect that usage pattern.
Angular testing utilities make it straightforward to investigate how injected services behave.

## Testing services with the `TestBed`

Your application relies on Angular [dependency injection (DI)](guide/di) to create services.
When a service has a dependent service, DI finds or creates that dependent service.
And if that dependent service has its own dependencies, DI finds-or-creates them as well.

As a service *consumer*, you don't worry about any of this.
You don't worry about the order of constructor arguments or how they're created.

As a service *tester*, you must at least think about the first level of service dependencies but you *can* let Angular DI do the service creation and deal with constructor argument order when you use the `TestBed` testing utility to provide and create services.

## Angular `TestBed`

The `TestBed` is the most important of the Angular testing utilities.
The `TestBed` creates a dynamically-constructed Angular *test* module that emulates an Angular [@NgModule](guide/ngmodules).

The `TestBed.configureTestingModule()` method takes a metadata object that can have most of the properties of an [@NgModule](guide/ngmodules).

To test a service, you set the `providers` metadata property with an array of the services that you'll test or mock.

<docs-code header="app/demo/demo.testbed.spec.ts (provide ValueService in beforeEach)" path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" visibleRegion="value-service-before-each"/>

Then inject it inside a test by calling `TestBed.inject()` with the service class as the argument.

HELPFUL: `TestBed.get()` was deprecated as of Angular version 9.
To help minimize breaking changes, Angular introduces a new function called `TestBed.inject()`, which you should use instead.

<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" visibleRegion="value-service-inject-it"/>

Or inside the `beforeEach()` if you prefer to inject the service as part of your setup.

<docs-code path="adev/src/content/examples/testing/src/app/demo/demo.testbed.spec.ts" visibleRegion="value-service-inject-before-each">

When testing a service with a dependency, provide the mock in the providers array.

In the following example, the mock is a spy object.

The test consumes that spy in the same way it did earlier.

Testing without beforeEach()

Most test suites in this guide call beforeEach() to set the preconditions for each it() test and rely on the TestBed to create classes and inject services.

There's another school of testing that never calls beforeEach() and prefers to create classes explicitly rather than use the TestBed.

Here's how you might rewrite one of the MasterService tests in that style.

Begin by putting re-usable, preparatory code in a setup function instead of beforeEach().

The setup() function returns an object literal with the variables, such as masterService, that a test might reference. You don't define semi-global variables (for example, let masterService: MasterService) in the body of the describe().

Then each test invokes setup() in its first line, before continuing with steps that manipulate the test subject and assert expectations.

Notice how the test uses destructuring assignment to extract the setup variables that it needs.

Many developers feel this approach is cleaner and more explicit than the traditional beforeEach() style.

Although this testing guide follows the traditional style and the default CLI schematics generate test files with beforeEach() and TestBed, feel free to adopt this alternative approach in your own projects.

Testing HTTP services

Data services that make HTTP calls to remote servers typically inject and delegate to the Angular HttpClient service for XHR calls.

You can test a data service with an injected HttpClient spy as you would test any service with a dependency.

IMPORTANT: The HeroService methods return Observables. You must subscribe to an observable to (a) cause it to execute and (b) assert that the method succeeds or fails.

The subscribe() method takes a success (next) and fail (error) callback. Make sure you provide both callbacks so that you capture errors. Neglecting to do so produces an asynchronous uncaught observable error that the test runner will likely attribute to a completely different test.

HttpClientTestingModule

Extended interactions between a data service and the HttpClient can be complex and difficult to mock with spies.

The HttpClientTestingModule can make these testing scenarios more manageable.

While the code sample accompanying this guide demonstrates HttpClientTestingModule, this page defers to the Http guide, which covers testing with the HttpClientTestingModule in detail.

Basics of testing components

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 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

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 DashboardHeroComponent?
  • Is the hero name displayed as expected (such as uppercase)?
  • Is the welcome message displayed by the template of WelcomeComponent?

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 as well as other testing helpers.

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 BannerComponent in the app/banner folder (with inline template and styles):

ng generate component banner --inline-template --inline-style --module app

It also generates an initial test file for the component, banner-external.component.spec.ts, that looks like this:

HELPFUL: Because compileComponents is asynchronous, it uses the waitForAsync utility function imported from @angular/core/testing.

Refer to the waitForAsync section for more details.

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:

In this example, the metadata object passed to TestBed.configureTestingModule simply declares BannerComponent, the component to test.

HELPFUL: There's no need to declare or import anything else. The default test module is pre-configured with something like the BrowserModule from @angular/platform-browser.

Later you'll call TestBed.configureTestingModule() with imports, providers, and more declarations to suit your testing needs. Optional override methods can further fine-tune aspects of the configuration.

createComponent()

After configuring TestBed, you call its createComponent() method.

TestBed.createComponent() creates an instance of the BannerComponent, adds a corresponding element to the test-runner DOM, and returns a ComponentFixture.

IMPORTANT: Do not re-configure TestBed after calling createComponent.

The createComponent method freezes the current TestBed definition, closing it to further configuration.

You cannot call any more TestBed configuration methods, not configureTestingModule(), nor get(), nor any of the override... methods. If you try, TestBed throws an error.

ComponentFixture

The 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 a Jasmine expectation:

beforeEach()

You will add more tests as this component evolves. Rather than duplicate the TestBed configuration for each test, you refactor to pull the setup into a Jasmine beforeEach() and some supporting variables:

Now add a test that gets the component's element from fixture.nativeElement and looks for the expected text.

nativeElement

The value of ComponentFixture.nativeElement has the any type. Later you'll encounter the 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 Web Worker, 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:

DebugElement

The Angular fixture provides the component's element directly through the fixture.nativeElement.

This is actually a convenience method, implemented as fixture.debugElement.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 abstraction to work safely across all supported platforms. Instead of creating an HTML element tree, Angular creates a DebugElement tree that wraps the native elements for the runtime platform. The nativeElement property unwraps the 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:

The DebugElement has other methods and properties that are useful in tests, as you'll see elsewhere in this guide.

You import the DebugElement symbol from the Angular core library.

By.css()

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 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 tree matches the selection criteria.

You create a predicate with the help of a By class imported from a library for the runtime platform. Here's the By import for the browser platform:

The following example re-implements the previous test with DebugElement.query() and the browser's By.css method.

Some noteworthy observations:

  • The By.css() static method selects DebugElement nodes with a standard CSS selector.
  • The query returns a 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 approach might be overkill.

It's often more straightforward and clear to filter with a standard HTMLElement method such as querySelector() or querySelectorAll().

Component testing scenarios

This guide explores common component testing use cases.

Component binding

In the example application, the BannerComponent presents static title text in the HTML template.

After a few changes, the BannerComponent presents a dynamic title by binding to the component's title property like this.

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>`

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.

<docs-code header="app/banner/banner.component.spec.ts (setup)" path="adev/src/content/examples/testing/src/app/banner/banner.component.spec.ts" visibleRegion="setup"/>

### `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:

<docs-code path="adev/src/content/examples/testing/src/app/banner/banner.component.spec.ts" visibleRegion="expect-h1-default-v1"/>

*That test fails* with the message:

<docs-code language="javascript">

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 does not trigger change detection by default; a fact confirmed in the revised test:

### `detectChanges()`

You can tell the `TestBed` to perform data binding by calling `fixture.detectChanges()`.
Only then does the `<h1>` have the expected title.

<docs-code path="adev/src/content/examples/testing/src/app/banner/banner.component.spec.ts" visibleRegion="expect-h1-default"/>

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.detectChanges()`.

<docs-code path="adev/src/content/examples/testing/src/app/banner/banner.component.spec.ts" visibleRegion="after-change"/>

### Automatic change detection

The `BannerComponent` tests frequently call `detectChanges`.
Many testers prefer that the Angular test environment run change detection automatically like it does in production.

That's possible by configuring the `TestBed` with the `ComponentFixtureAutoDetect` provider.
First import it from the testing utility library:

<docs-code header="app/banner/banner.component.detect-changes.spec.ts (import)" path="adev/src/content/examples/testing/src/app/banner/banner.component.detect-changes.spec.ts" visibleRegion="import-ComponentFixtureAutoDetect"/>

Then add it to the `providers` array of the testing module configuration:

<docs-code header="app/banner/banner.component.detect-changes.spec.ts (AutoDetect)" path="adev/src/content/examples/testing/src/app/banner/banner.component.detect-changes.spec.ts" visibleRegion="auto-detect"/>

HELPFUL: You can also use the `fixture.autoDetectChanges()` function instead if you only want to enable automatic change detection
after making updates to the state of the fixture's component. In addition, automatic change detection is on by default
when using `provideZonelessChangeDetection` and turning it off is not recommended.

Here are three tests that illustrate how automatic change detection works.

<docs-code header="app/banner/banner.component.detect-changes.spec.ts (AutoDetect Tests)" path="adev/src/content/examples/testing/src/app/banner/banner.component.detect-changes.spec.ts" visibleRegion="auto-detect-tests"/>

The first test shows the benefit of automatic change detection.

The second and third test reveal an important limitation.
The Angular testing environment does not run change detection synchronously when updates happen inside the test case that changed the component's `title`.
The test must call `await fixture.whenStable` to wait for another round of change detection.

HELPFUL: Angular does not know about direct updates to values that are not signals. The easiest way to ensure that
change detection will be scheduled is to use signals for values read in the template.

### 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 demonstrates the proper sequence.

<docs-code header="app/hero/hero-detail.component.spec.ts (pipe test)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" visibleRegion="title-case-pipe"/>

## Component with external files

The preceding `BannerComponent` is defined with an *inline template* and *inline css*, specified in the `@Component.template` and `@Component.styles` properties respectively.

Many components specify *external templates* and *external css* with the `@Component.templateUrl` and `@Component.styleUrls` properties respectively, as the following variant of `BannerComponent` does.

<docs-code header="app/banner/banner-external.component.ts (metadata)" path="adev/src/content/examples/testing/src/app/banner/banner-external.component.ts" visibleRegion="metadata"/>

This syntax tells the Angular compiler to read the external files during component compilation.

That's not a problem when you run the CLI `ng test` command because it *compiles the application before running the tests*.

However, if you run the tests in a **non-CLI environment**, tests of this component might fail.
For example, if you run the `BannerComponent` tests in a web coding environment such as [plunker](https://plnkr.co), you'll see a message like this one:

<docs-code hideCopy language="shell">

Error: This test module uses the component BannerComponent
which is using a "templateUrl" or "styleUrls", but they were never compiled.
Please call "TestBed.compileComponents" before your test.

You get this test failure message when the runtime environment compiles the source code during the tests themselves.

To correct the problem, call compileComponents() as explained in the following Calling compileComponents section.

Component with a dependency

Components often have service dependencies.

The WelcomeComponent displays a welcome message to the logged-in user. It knows who the user is based on a property of the injected UserService:

The `WelcomeComponent` has decision logic that interacts with the service, logic that makes this component worth testing.

### Provide service test doubles

A *component-under-test* doesn't have to be provided with real services.

Injecting the real `UserService` 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

The tests need access to the `UserService` injected into the `WelcomeComponent`.

Angular has a hierarchical injection system.
There can be injectors at multiple levels, from the root injector created by the `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`.

<docs-code header="WelcomeComponent's injector" path="adev/src/content/examples/testing/src/app/welcome/welcome.component.spec.ts" visibleRegion="injected-service"/>

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()` (see below).

### `TestBed.inject()`

This is easier to remember and less verbose than retrieving a service using the fixture's `DebugElement`.

In this test suite, the *only* provider of `UserService` is the root testing module, so it is safe to call `TestBed.inject()` as follows:

<docs-code header="TestBed injector" path="adev/src/content/examples/testing/src/app/welcome/welcome.component.spec.ts" visibleRegion="inject-from-testbed" />

HELPFUL: For a use case in which `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

Here's the complete `beforeEach()`, using `TestBed.inject()`:

<docs-code header="app/welcome/welcome.component.spec.ts" path="adev/src/content/examples/testing/src/app/welcome/welcome.component.spec.ts" visibleRegion="setup"/>

And here are some tests:

<docs-code header="app/welcome/welcome.component.spec.ts" path="adev/src/content/examples/testing/src/app/welcome/welcome.component.spec.ts" visibleRegion="tests"/>

The first is a sanity test; it confirms that the `UserService` is called and working.

HELPFUL: The withContext function \(for example, `'expected name'`\) is an optional failure label.
If the expectation fails, Jasmine 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

In this sample, the `AboutComponent` template hosts a `TwainComponent`.
The `TwainComponent` displays Mark Twain quotes.

<docs-code header="app/twain/twain.component.ts (template)" path="adev/src/content/examples/testing/src/app/twain/twain.component.ts" visibleRegion="template" />

HELPFUL: The value of the component's `quote` property passes through an `AsyncPipe`.
That means the property returns either a `Promise` or an `Observable`.

In this example, the `TwainComponent.getQuote()` method tells you that the `quote` property returns an `Observable`.

<docs-code header="app/twain/twain.component.ts (getQuote)" path="adev/src/content/examples/testing/src/app/twain/twain.component.ts" visibleRegion="get-quote"/>

The `TwainComponent` gets quotes from an injected `TwainService`.
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 with a spy

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.
The setup in this `app/twain/twain.component.spec.ts` shows one way to do that:

<docs-code header="app/twain/twain.component.spec.ts (setup)" path="adev/src/content/examples/testing/src/app/twain/twain.component.spec.ts" visibleRegion="setup"/>

Focus on the spy.

<docs-code path="adev/src/content/examples/testing/src/app/twain/twain.component.spec.ts" visibleRegion="spy"/>

The spy is designed such 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.

You can write many useful tests with this spy, even though its `Observable` is synchronous.

HELPFUL: It is best to limit the usage of spies to only what is necessary for the test. Creating mocks or spies for more than what's necessary can be brittle. As the component and injectable evolves, the unrelated tests can fail because they no longer mock enough behaviors that would otherwise not affect the test.
### Async test with `fakeAsync()`

To use `fakeAsync()` functionality, you must import `zone.js/testing` in your test setup file.
If you created your project with the Angular CLI, `zone-testing` is already imported in `src/test.ts`.

The following test confirms the expected behavior when the service returns an `ErrorObservable`.

<docs-code path="adev/src/content/examples/testing/src/app/twain/twain.component.spec.ts" visibleRegion="error-test"/>

HELPFUL: The `it()` function receives an argument of the following form.

<docs-code language="javascript">

fakeAsync(() => { /*test body*/ })

The fakeAsync() function enables a linear coding style by running the test body in a special fakeAsync test zone. The test body appears to be synchronous. There is no nested syntax (like a Promise.then()) to disrupt the flow of control.

HELPFUL: Limitation: The fakeAsync() function won't work if the test body makes an XMLHttpRequest (XHR) call. XHR calls within a test are rare, but if you need to call XHR, see the waitForAsync() section.

IMPORTANT: Be aware that asynchronous tasks that happen inside the fakeAsync zone need to be manually executed with flush or tick. If you attempt to wait for them to complete (i.e. using fixture.whenStable) without using the fakeAsync test helpers to advance time, your test will likely fail. See below for more information.

The tick() function

You do have to call tick() to advance the virtual clock.

Calling tick() simulates the passage of time until all pending asynchronous activities finish. In this case, it waits for the observable's setTimeout().

The tick() function accepts millis and tickOptions as parameters. The millis parameter specifies how much the virtual clock advances and defaults to 0 if not provided. For example, if you have a setTimeout(fn, 100) in a fakeAsync() test, you need to use tick(100) to trigger the fn callback. The optional tickOptions parameter has a property named processNewMacroTasksSynchronously. The processNewMacroTasksSynchronously property represents whether to invoke new generated macro tasks when ticking and defaults to true.

The [tick()](api/core/testing/tick) function is one of the Angular testing utilities that you import with `TestBed`.
It's a companion to `fakeAsync()` and you can only call it within a `fakeAsync()` body.

### tickOptions

In this example, you have a new macro task, the nested `setTimeout` function. By default, when the `tick` is setTimeout, `outside` and `nested` will both be triggered.

<docs-code path="adev/src/content/examples/testing/src/app/demo/async-helper.spec.ts" visibleRegion="fake-async-test-tick-new-macro-task-sync"/>

In some case, you don't want to trigger the new macro task when ticking. You can use `tick(millis, {processNewMacroTasksSynchronously: false})` to not invoke a new macro task.

<docs-code path="adev/src/content/examples/testing/src/app/demo/async-helper.spec.ts" visibleRegion="fake-async-test-tick-new-macro-task-async"/>

### Comparing dates inside fakeAsync()

`fakeAsync()` simulates passage of time, which lets you calculate the difference between dates inside `fakeAsync()`.

<docs-code path="adev/src/content/examples/testing/src/app/demo/async-helper.spec.ts" visibleRegion="fake-async-test-date"/>

### jasmine.clock with fakeAsync()

Jasmine also provides a `clock` feature to mock dates.
Angular automatically runs tests that are run after `jasmine.clock().install()` is called inside a `fakeAsync()` method until `jasmine.clock().uninstall()` is called.
`fakeAsync()` is not needed and throws an error if nested.

By default, this feature is disabled.
To enable it, set a global flag before importing `zone-testing`.

If you use the Angular CLI, configure this flag in `src/test.ts`.

<docs-code language="typescript">

[window as any]('&lowbar;&lowbar;zone&lowbar;symbol__fakeAsyncPatchLock') = true;
import 'zone.js/testing';
### Using the RxJS scheduler inside fakeAsync()

You can also use RxJS scheduler in `fakeAsync()` just like using `setTimeout()` or `setInterval()`, but you need to import `zone.js/plugins/zone-patch-rxjs-fake-async` to patch RxJS scheduler.

<docs-code path="adev/src/content/examples/testing/src/app/demo/async-helper.spec.ts" visibleRegion="fake-async-test-rxjs"/>

### Support more macroTasks

By default, `fakeAsync()` supports the following macro tasks.

* `setTimeout`
* `setInterval`
* `requestAnimationFrame`
* `webkitRequestAnimationFrame`
* `mozRequestAnimationFrame`

If you run other macro tasks such as `HTMLCanvasElement.toBlob()`, an *"Unknown macroTask scheduled in fake async test"* error is thrown.
If you want to support such a case, you need to define the macro task you want to support in `beforeEach()`.
For example:

<docs-code header="src/app/shared/canvas.component.spec.ts (excerpt)" path="adev/src/content/examples/testing/src/app/shared/canvas.component.spec.ts" visibleRegion="enable-toBlob-macrotask"/>

HELPFUL: In order to make the `<canvas>` element Zone.js-aware in your app, you need to import the `zone-patch-canvas` patch \(either in `polyfills.ts` or in the specific file that uses `<canvas>`\):

<docs-code header="src/polyfills.ts or src/app/shared/canvas.component.ts" path="adev/src/content/examples/testing/src/app/shared/canvas.component.ts" visibleRegion="import-canvas-patch"/>

### Async observables

You might be satisfied with the test coverage of these tests.

However, you might be troubled by the fact that the real service doesn't quite behave this way.
The real service sends requests to a remote server.
A server takes time to respond and the response certainly won't be available immediately as in the previous two tests.

Your tests will reflect the real world more faithfully if you return an *asynchronous* observable from the `getQuote()` spy like this.

<docs-code path="adev/src/content/examples/testing/src/app/twain/twain.component.spec.ts" visibleRegion="async-setup"/>

### Async observable helpers

The async observable was produced by an `asyncData` helper.
The `asyncData` helper is a utility function that you'll have to write yourself, or copy this one from the sample code.

<docs-code header="testing/async-observable-helpers.ts" path="adev/src/content/examples/testing/src/testing/async-observable-helpers.ts" visibleRegion="async-data"/>

This helper's observable emits the `data` value in the next turn of the JavaScript engine.

The [RxJS `defer()` operator](http://reactivex.io/documentation/operators/defer.html) returns an observable.
It takes a factory function that returns either a promise or an observable.
When something subscribes to *defer*'s observable, it adds the subscriber to a new observable created with that factory.

The `defer()` operator transforms the `Promise.resolve()` into a new observable that, like `HttpClient`, emits once and completes.
Subscribers are unsubscribed after they receive the data value.

There's a similar helper for producing an async error.

<docs-code path="adev/src/content/examples/testing/src/testing/async-observable-helpers.ts" visibleRegion="async-error"/>

### More async tests

Now that the `getQuote()` spy is returning async observables, most of your tests will have to be async as well.

Here's a `fakeAsync()` test that demonstrates the data flow you'd expect in the real world.

<docs-code path="adev/src/content/examples/testing/src/app/twain/twain.component.spec.ts" visibleRegion="fake-async-test"/>

Notice that the quote element displays the placeholder value \(`'...'`\) after `ngOnInit()`.
The first quote hasn't arrived yet.

To flush the first quote from the observable, you call [tick()](api/core/testing/tick).
Then call `detectChanges()` to tell Angular to update the screen.

Then you can assert that the quote element displays the expected text.

### Async test without `fakeAsync()`

Here's the previous `fakeAsync()` test, re-written with the `async`.

<docs-code path="adev/src/content/examples/testing/src/app/twain/twain.component.spec.ts" visibleRegion="async-test"/>

### `whenStable`

The test must wait for the `getQuote()` observable to emit the next quote.
Instead of calling [tick()](api/core/testing/tick), it calls `fixture.whenStable()`.

The `fixture.whenStable()` returns a promise that resolves when the JavaScript engine's task queue becomes empty.
In this example, the task queue becomes empty when the observable emits the first quote.
## 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 `DashboardHeroComponent` is a tiny example of a component in this role.
It displays an individual hero provided by the `DashboardComponent`.
Clicking that hero tells the `DashboardComponent` that the user has selected the hero.

The `DashboardHeroComponent` is embedded in the `DashboardComponent` template like this:

<docs-code header="app/dashboard/dashboard.component.html (excerpt)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard.component.html" visibleRegion="dashboard-hero"/>

The `DashboardHeroComponent` 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:

<docs-code header="app/dashboard/dashboard-hero.component.ts (component)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.ts" visibleRegion="component"/>

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 `DashboardComponent`
* Test it as a standalone component
* Test it as used by a substitute for `DashboardComponent`

The immediate goal is to test the `DashboardHeroComponent`, not the `DashboardComponent`, so, try the second and third options.

### Test `DashboardHeroComponent` standalone

Here's the meat of the spec file setup.

<docs-code header="app/dashboard/dashboard-hero.component.spec.ts (setup)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="setup"/>

Notice how the setup code assigns a test hero \(`expectedHero`\) to the component's `hero` property, emulating the way the `DashboardComponent` 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.

<docs-code path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="name-test"/>

Because the [template](#dashboard-hero-component) passes the hero name through the Angular `UpperCasePipe`, the test must match the element value with the upper-cased name.

### Clicking

Clicking the hero should raise a `selected` event that the host component \(`DashboardComponent` presumably\) can hear:

<docs-code path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="click-test"/>

The component's `selected` property returns an `EventEmitter`, which looks like an RxJS synchronous `Observable` to consumers.
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`

The `heroDe` in the previous test is a `DebugElement` that represents the hero `<div>`.

It has Angular properties and methods that abstract interaction with the native element.
This test calls the `DebugElement.triggerEventHandler` with the "click" event name.
The "click" event binding responds by calling `DashboardHeroComponent.click()`.

The Angular `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.

<docs-code path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="trigger-event-handler"/>

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` directive expects an object with a `button` property that identifies which mouse button, if any, was pressed during the click.
The `RouterLink` directive throws an error if the event object is missing.

### Click the element

The following test alternative calls the native element's own `click()` method, which is perfectly fine for *this component*.

<docs-code path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="click-test-2"/>

### `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:

<docs-code header="testing/index.ts (click helper)" path="adev/src/content/examples/testing/src/testing/index.ts" visibleRegion="click-event"/>

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` 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.

<docs-code header="app/dashboard/dashboard-hero.component.spec.ts (test with click helper)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="click-test-3"/>

## Component inside a test host

The previous tests played the role of the host `DashboardComponent` themselves.
But does the `DashboardHeroComponent` work correctly when properly data-bound to a host component?

<docs-code header="app/dashboard/dashboard-hero.component.spec.ts (test host)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="test-host"/>

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 `DashboardHeroComponent.selected` event emitted the expected hero.

The setup for the `test-host` tests is similar to the setup for the stand-alone tests:

<docs-code header="app/dashboard/dashboard-hero.component.spec.ts (test host setup)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="test-host-setup"/>

This testing module configuration shows two important differences:

* It *creates* the `TestHostComponent` instead of the `DashboardHeroComponent`
* The `TestHostComponent` sets the `DashboardHeroComponent.hero` with a binding

The `createComponent` returns a `fixture` that holds an instance of `TestHostComponent` instead of an instance of `DashboardHeroComponent`.

Creating the `TestHostComponent` has the side effect of creating a `DashboardHeroComponent` 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:

<docs-code header="app/dashboard/dashboard-hero.component.spec.ts (test-host)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard-hero.component.spec.ts" visibleRegion="test-host-tests"/>

Only the selected event test differs.
It confirms that the selected `DashboardHeroComponent` hero really does find its way up through the event binding to the host component.

## Routing component

A *routing component* is a component that tells the `Router` to navigate to another component.
The `DashboardComponent` is a *routing component* because the user can navigate to the `HeroDetailComponent` 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`. The `provideRouter` function can be used directly in the test module as well.

<docs-code header="app/dashboard/dashboard.component.spec.ts" path="adev/src/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts" visibleRegion="router-harness"/>

The following test clicks the displayed hero and confirms that we navigate to the expected URL.

<docs-code header="app/dashboard/dashboard.component.spec.ts (navigate test)" path="adev/src/content/examples/testing/src/app/dashboard/dashboard.component.spec.ts" visibleRegion="navigate-test"/>

## Routed components

A *routed component* is the destination of a `Router` navigation.
It can be trickier to test, especially when the route to the component *includes parameters*.
The `HeroDetailComponent` is a *routed component* that is the destination of such a route.

When a user clicks a *Dashboard* hero, the `DashboardComponent` tells the `Router` to navigate to `heroes/:id`.
The `:id` is a route parameter whose value is the `id` of the hero to edit.

The `Router` matches that URL to a route to the `HeroDetailComponent`.
It creates an `ActivatedRoute` object with the routing information and injects it into a new instance of the `HeroDetailComponent`.

Here are the services injected into `HeroDetailComponent`:

<docs-code header="app/hero/hero-detail.component.ts (inject)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.ts" visibleRegion="inject"/>

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` property which is an `Observable`.

It can't just reference the `id` property of the `ActivatedRoute.paramMap`.
The component has to *subscribe* to the `ActivatedRoute.paramMap` observable and be prepared for the `id` to change during its lifetime.

<docs-code header="app/hero/hero-detail.component.ts (constructor)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.ts" visibleRegion="ctor"/>

Tests can explore how the `HeroDetailComponent` responds to different `id` parameter values by navigating to different routes.

## 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 `AppComponent`, for example, displays a navigation bar with anchors and their `RouterLink` directives.

<docs-code header="app/app.component.html" path="adev/src/content/examples/testing/src/app/app.component.html"/>

To validate the links but not the navigation, you don't need the `Router` to navigate and you don't need the `<router-outlet>` to mark where the `Router` inserts *routed components*.

The `BannerComponent` and `WelcomeComponent` \(indicated by `<app-banner>` and `<app-welcome>`\) are also irrelevant.

Yet any test that creates the `AppComponent` in the DOM also creates instances of these three components and, if you let that happen, you'll have to configure the `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 `AppComponent` 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

In the first technique, you create and declare stub versions of the components and directive that play little or no role in the tests.

<docs-code header="app/app.component.spec.ts (stub declaration)" path="adev/src/content/examples/testing/src/app/app.component.spec.ts" visibleRegion="component-stubs"/>

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`. 

<docs-code header="app/app.component.spec.ts (TestBed stubs)" path="adev/src/content/examples/testing/src/app/app.component.spec.ts" visibleRegion="testbed-stubs"/>

HELPFUL: The `set` key in this example replaces all the exisiting imports on your component, make sure to imports all dependencies, not only the stubs. Alternatively you can use the `remove`/`add` keys to selectively remove and add imports.

### `NO_ERRORS_SCHEMA`

In the second approach, add `NO_ERRORS_SCHEMA` to the metadata overrides of your component.

<docs-code header="app/app.component.spec.ts (NO_ERRORS_SCHEMA)" path="adev/src/content/examples/testing/src/app/app.component.spec.ts" visibleRegion="no-errors-schema"/>

The `NO_ERRORS_SCHEMA` tells the Angular compiler to ignore unrecognized elements and attributes.

The compiler recognizes the `<app-root>` element and the `routerLink` attribute because you declared a corresponding `AppComponent` and `RouterLink` in the `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

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` approach is the easier of the two but don't overuse it.

The `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.

<docs-code header="app/app.component.spec.ts (mixed setup)" path="adev/src/content/examples/testing/src/app/app.component.spec.ts" visibleRegion="mixed-setup"/>

The Angular compiler creates the `BannerStubComponent` for the `<app-banner>` element and applies the `RouterLink` to the anchors with the `routerLink` attribute, but it ignores the `<app-welcome>` and `<router-outlet>` tags.

### `By.directive` and injected directives

A little more setup triggers the initial data binding and gets references to the navigation links:

<docs-code header="app/app.component.spec.ts (test setup)" path="adev/src/content/examples/testing/src/app/app.component.spec.ts" visibleRegion="test-setup"/>

Three points of special interest:

* Locate the anchor elements with an attached directive using `By.directive`
* The query returns `DebugElement` wrappers around the matching elements
* Each `DebugElement` exposes a dependency injector with the specific instance of the directive attached to that element

The `AppComponent` links to validate are as follows:

<docs-code header="app/app.component.html (navigation links)" path="adev/src/content/examples/testing/src/app/app.component.html" visibleRegion="links"/>

Here are some tests that confirm those links are wired to the `routerLink` directives as expected:

<docs-code header="app/app.component.spec.ts (selected tests)" path="adev/src/content/examples/testing/src/app/app.component.spec.ts" visibleRegion="tests"/>

## Use a `page` object

The `HeroDetailComponent` 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.

<docs-code
  path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.html" header="app/hero/hero-detail.component.html"/>

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`

<docs-code header="app/hero/hero-detail.component.spec.ts (Page)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" visibleRegion="page"/>

Now the important hooks for component manipulation and inspection are neatly organized and accessible from an instance of `Page`.

A `createComponent` method creates a `page` object and fills in the blanks once the `hero` arrives.

<docs-code header="app/hero/hero-detail.component.spec.ts (createComponent)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" visibleRegion="create-component"/>

Here are a few more `HeroDetailComponent` tests to reinforce the point.

<docs-code header="app/hero/hero-detail.component.spec.ts (selected tests)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" visibleRegion="selected-tests"/>

## Override component providers

The `HeroDetailComponent` provides its own `HeroDetailService`.

<docs-code header="app/hero/hero-detail.component.ts (prototype)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.ts" visibleRegion="prototype"/>

It's not possible to stub the component's `HeroDetailService` in the `providers` of the `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` 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`.

<docs-code header="app/hero/hero-detail.service.ts (prototype)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.service.ts" visibleRegion="prototype"/>

The [previous test configuration](#import-a-feature-module) 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` method can replace the component's `providers` with easy-to-manage *test doubles* as seen in the following setup variation:

<docs-code header="app/hero/hero-detail.component.spec.ts (Override setup)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" visibleRegion="setup-override"/>

Notice that `TestBed.configureTestingModule` no longer provides a fake `HeroService` because it's [not needed](#spy-stub).

### The `overrideComponent` method

Focus on the `overrideComponent` method.

<docs-code header="app/hero/hero-detail.component.spec.ts (overrideComponent)" path="adev/src/content/examples/testing/src/app/hero/hero-detail.component.spec.ts" visibleRegion="override-component-method"/>

It takes two arguments: the component type to override \(`HeroDetailComponent`\) and an override metadata object.
The [override metadata object](guide/testing/utility-apis#metadata-override-object) is a generic defined as follows:

<docs-code language="javascript">

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 decorator:

selector?: string;
template?: string;
templateUrl?: string;
providers?: any[];

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 HeroDetailComponent tests will assert that methods of the HeroDetailService were called by spying on the service methods. Accordingly, the stub implements its methods as spies:

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.

More overrides

The TestBed.overrideComponent method can be called multiple times for the same or different components. The 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.

Debugging tests

If your tests aren't working as you expect them to, you can inspect and debug them in the browser.

Debug specs in the browser in the same way that you debug an application.

  1. Reveal the Karma browser window. See Set up 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.

Karma debugging

# Testing Utility APIs

This page describes the most useful Angular testing features.

The Angular testing utilities include the TestBed, the ComponentFixture, and a handful of functions that control the test environment. The TestBed and ComponentFixture classes are covered separately.

Here's a summary of the stand-alone functions, in order of likely utility:

Function Details
waitForAsync Runs the body of a test (it) or setup (beforeEach) function within a special async test zone. See waitForAsync.
fakeAsync Runs the body of a test (it) within a special fakeAsync test zone, enabling a linear control flow coding style. See fakeAsync.
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". Accepts an optional argument that moves the virtual clock forward by the specified number of milliseconds, clearing asynchronous activities scheduled within that timeframe. See tick.
inject Injects one or more services from the current TestBed injector into a test function. It cannot inject a service provided by the component itself. See discussion of the debugElement.injector.
discardPeriodicTasks When a fakeAsync() test ends with pending timer event tasks (queued setTimeOut and setInterval callbacks), the test fails with a clear error message.
In general, a test should end with no queued tasks. When pending timer tasks are expected, call discardPeriodicTasks to flush the task queue and avoid the error.
flushMicrotasks When a 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 to flush the micro-task queue and avoid the error.
ComponentFixtureAutoDetect A provider token for a service that turns on automatic change detection.
getTestBed Gets the current instance of the TestBed. Usually unnecessary because the static class methods of the TestBed class are typically sufficient. The TestBed instance exposes a few rarely used members that are not available as static methods.

TestBed class summary

The 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 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, @Component, @Directive, or @Pipe.

type MetadataOverride<T> = {
  add?: Partial<T>;
  remove?: Partial<T>;
  set?: Partial<T>;
};

The TestBed API consists of static class methods that either update or reference a global instance of the TestBed.

Internally, all static methods cover methods of the current runtime TestBed instance, which is also returned by the getTestBed() function.

Call 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 (karma-test-shim, browser-test-shim) establish the initial test environment 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 a templateUrl or styleUrls because fetching component template and style files is necessarily asynchronous. See compileComponents.
After calling compileComponents, the 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 configuration. After calling createComponent, the TestBed configuration is frozen for the duration of the current spec.
overrideModule Replace metadata for the given 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.
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.

| inject | Retrieve a service from the current 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() method takes an optional second parameter, the object to return if Angular can't find the provider (null in this example): After calling TestBed.inject, the TestBed configuration is frozen for the duration of the current spec. | | initTestEnvironment | Initialize the testing environment for the entire test run.
The testing shims (karma-test-shim, browser-test-shim) 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 the Angular compiler factory, a PlatformRef, and a default Angular testing module. Alternatives for non-browser platforms are available in the general form @angular/platform-<platform_name>/testing/<platform_name>. | | resetTestEnvironment | Reset the initial test environment, including the default testing module. |

A few of the TestBed instance methods are not covered by static TestBed class methods. These are rarely needed.

The ComponentFixture

The TestBed.createComponent<T> creates an instance of the component T and returns a strongly typed ComponentFixture for that component.

The ComponentFixture properties and methods provide access to the component, its DOM representation, and aspects of its Angular environment.

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.
debugElement The 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.
nativeElement The native DOM element at the root of the component.
changeDetectorRef The ChangeDetectorRef for the component.
The ChangeDetectorRef is most valuable when testing a component that has the ChangeDetectionStrategy.OnPush method or the component's change detection is under your programmatic control.

ComponentFixture methods

The fixture methods cause Angular to perform certain tasks on the component tree. Call these method 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 false. Testers who prefer fine control over test behavior tend to keep it false.
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.
destroy Trigger component destruction.

DebugElement

The DebugElement provides crucial insights into the component's DOM representation.

From the test root component's DebugElement returned by fixture.debugElement, you can walk (and query) the fixture's entire element and component subtrees.

Here are the most useful 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 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 children. Walk the tree by descending through children. DebugElement also has childNodes, a list of DebugNode objects. DebugElement derives from DebugNode objects and there are often more nodes than elements. Testers can usually ignore plain nodes.
parent The 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. See triggerEventHandler.
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 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) and DebugElement.queryAll(predicate) methods take a predicate that filters the source element's subtree for matching DebugElement.

The predicate is any method that takes a DebugElement and returns a truthy value. The following example finds all DebugElements with a reference to a template local variable named "content":

The Angular By class has three static methods for common predicates:

Static method Details
By.all Return all elements
By.css(selector) Return elements with matching CSS selectors
By.directive(directive) Return elements that Angular matched to an instance of the directive class
# 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 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
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
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. Adding support for additional testing environments

For the full API reference, please see the Angular CDK's component harness API reference page.

Using component harnesses in tests

Before you start

TIP: This guide assumes you've already read the component harnesses overview guide. Read that first if you're new to using component harnesses.

CDK Installation

The Component Dev Kit (CDK) 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

You can use component test harnesses in different test environments. Angular CDK supports two built-in environments:

  • Unit tests with Angular's TestBed
  • End-to-end tests with 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 for more information.

Using the loader from TestbedHarnessEnvironment for unit tests

For unit tests you can create a harness loader from TestbedHarnessEnvironment. This environment uses a component fixture created by Angular's 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

For WebDriver-based end-to-end tests you can create a harness loader with 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

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 for the first instance of the element, use the getHarness() method. To get all 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.getHarnesses(MyComponent);

In addition to getHarness and getAllHarnesses, 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

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

To interact with elements below the root element of this harness loader, use the 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

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 class with predicates functions that can be used to filter component instances, to do so.

When you ask a 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, which gets harnesses that are filtered based on one or more conditions

HarnessPredicate does support some base filters (selector, ancestor) that work on anything that extends 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.

// 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

While every harness defines an API specific to its corresponding component, they all share a common base class, 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:

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

By default, test harnesses runs Angular's change detection 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 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 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 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);
});

Creating harnesses for your components

Before you start

TIP: This guide assumes you've already read the component harnesses overview guide. Read that first if you're new to using component harnesses.

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

The Component Dev Kit (CDK) 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

The abstract ComponentHarness class is the base class for all component harnesses. To create a custom component harness, extend 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 or 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></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 subclasses require only the hostSelector property, most harnesses should also implement a static with method to generate HarnessPredicate instances. The filtering harnesses section covers this in more detail.

Finding elements in the component's DOM

Each instance of a 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 base class.

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 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

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 has a number of methods to interact with the underlying DOM, such as blur(), click(), getAttribute(), and more. See the TestElement API reference page for the full list of methods.

Do not expose TestElement instances to harness users unless it's an element the component consumer defines directly, such as the component's host element. Exposing 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

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 has an alternate signature that can be used for locating sub-harnesses rather than elements.

See the ComponentHarness API reference page 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></ng-content>
    </my-popup>
  `
})
class MyMenu {
  triggerText = input('');

  @ContentChildren(MyMenuItem) items: QueryList<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

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 class can capture criteria like this for a ComponentHarness subclass. While the test author is able to construct HarnessPredicate instances manually, it's easier when the ComponentHarness subclass provides a helper method to construct predicates for common filters.

You should create a static with() method on each ComponentHarness subclass that returns a 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 interface and additional optional properties as needed. HarnessPredicate provides several convenience methods for adding options: stringMatches(), addOption(), and add(). See the HarnessPredicate API page 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 instead of a ComponentHarness class to any of the APIs on HarnessLoader, LocatorFactory, or 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 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

Some components project additional content into the component's template. See the content projection guide for more information.

Add a 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 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 for more details.

For example, the MyPopupHarness example from above can extend 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

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 provides a method that can be used to get a LocatorFactory for the root element of the document. The LocatorFactory supports most of the same APIs as the 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

The methods on TestElement automatically trigger Angular's change detection and wait for tasks inside the 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 stabilization before animation events are fully flushed. In cases where this is needed, the ComponentHarness offers a forceStabilize() method that can be called to do the second round.

You can use 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 since this does not happen automatically.

Animating your applications with animate.enter and animate.leave

Well-designed animations can make your application more fun and straightforward to use, but they aren't just cosmetic. Animations can improve your application and 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 on elements directly and can also be used as a host binding.

animate.enter

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.

When the animation completes, Angular removes the class or classes that you specified in animate.enter from the DOM. Animation classes are only be 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.

animate.leave

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.

When the animation completes, Angular automatically removes the animated element from the DOM.

NOTE: When using multiple keyframe animations or transition properties on a 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.

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, anime.js, or any other JavaScript animation library.

The $event object has the type 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 in milliseconds.

{ provide: MAX_ANIMATION_TIMEOUT, useValue: 6000 }

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

You might also be interested in the following:

Animating your Application with 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

If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here's a few of them:
MDN's CSS Animations guide
W3Schools CSS3 Animations guide
The Complete CSS Animations Tutorial
CSS Animation for Beginners

and a couple of videos:
Learn CSS Animation in 9 Minutes
Net Ninja CSS Animation Tutorial Playlist

Check some of these various guides and tutorials out, and then come back to this guide.

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.

Adding the class animated-class to an element would trigger the animation on that element.

Animating a Transition

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 using a keyframe animation or transition styling.

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.

You can see similar examples in the template guide for animating styles directly.

Transitions, Timing, and Easing

Animating often requires adjusting timing, delays and easeing 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.

Similarly, you can use transition-duration, transition-delay, and transition-timing-function and the transition shorthand for animations that are not using @keyframes.

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:

Transition and Triggers

Animating Auto Height

You can use css-grid to animate to auto height.

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 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

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.

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.

For more information on animate.enter and animate.leave, see the Enter and Leave animations guide.

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.

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.

  1. Use the prefers-reduced-motion media query to ensure no animations play for users that prefer less animation.

  2. Prevent adding animation classes programatically

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.

OnAnimationStart
OnAnimationEnd
OnAnimationIteration
OnAnimationCancel

OnTransitionStart
OnTransitionRun
OnTransitionEnd
OnTransitionCancel

The Web Animations API has a lot of additional functionality. Take a look at the documentation to see all the available animation APIs.

NOTE: Be aware of bubbling issues with these callbacks. If you are animating children and parents, the events bubble up from children to parents. Consider stopping propagation or looking at more details within the event to determine if you're responding to the desired event target rather than an event bubbling up from a child node. You can examine the animationname property or the properties being transitioned to verify you have the right nodes.

Complex Sequences

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

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.

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

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 above.

Programmatic control of animations

You can retrieve animations off an element directly using Element.getAnimations(). This returns an array of every Animation on that element. You can use the Animation API to do much more than you could with what the 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

You might also be interested in the following:

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 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. Native View Transitions are a relatively new browser feature with limited support across all browsers.

How View Transitions work

View transitions use the browser's native document.startViewTransition API 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.

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, guards, and 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. 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

Enable view transitions by adding the withViewTransitions feature to your router configuration. Angular supports both standalone and NgModule bootstrap approaches:

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

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

This example demonstrates how router navigation can replace direct startViewTransition calls for counter updates.

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 scopes component styles, which prevents them from targeting the transition pseudo-elements correctly.

Try the updated “count” example on StackBlitz

Advanced transition control with onViewTransitionCreated

The withViewTransitions feature accepts an options object with an onViewTransitionCreated callback for advanced control over view transitions. This callback:

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 } from '@angular/router';

withViewTransitions({
  onViewTransitionCreated: ({transition}) => {
    const router = inject(Router);
    const targetUrl = router.getCurrentNavigation()!.finalUrl!;

    // Skip transition if only fragment or query params change
    const config = {
      paths: 'exact',
      matrixParams: 'exact',
      fragment: 'ignored',
      queryParams: 'ignored',
    };

    if (router.isActive(targetUrl, config)) {
      transition.skipTransition();
    }
  },
})

This example skips the view transition when navigation only changes the URL fragment or 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

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

Elements can transition smoothly between different DOM elements as long as they share the same view-transition-name.

Custom entry and exit animations

Create unique animations for elements entering and leaving the viewport during route transitions.

Async DOM updates and waiting for content

Angular Router prioritizes immediate transitions over waiting for additional content to load.

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

Use view transition types to apply different animation styles based on navigation context.

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.

Transitioning without freezing other animations

Maintain other page animations during view transitions to create more dynamic user experiences.

Animating with JavaScript

Control view transitions programmatically using JavaScript APIs for complex animation scenarios.

Migrating away from Angular's Animations package

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

If you've never written any native CSS animations, there are a number of excellent guides to get you started. Here's a few of them:
MDN's CSS Animations guide
W3Schools CSS3 Animations guide
The Complete CSS Animations Tutorial
CSS Animation for Beginners

and a couple of videos:
Learn CSS Animation in 9 Minutes
Net Ninja CSS Animation Tutorial Playlist

Check some of these various guides and tutorials out, and then come back to this guide.

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() 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 Native CSS

Adding the class animated-class to an element would trigger the animation on that element.

Animating a Transition

Animating State and Styles

The animations package allowed you to define various states using the 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

This same behavior can be accomplished natively by using CSS classes either using a keyframe animation or transition styling.

With Native CSS

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.

You can see similar examples in the template guide for animating styles directly.

Transitions, Timing, and Easing

The animations package 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.

Similarly, you can use transition-duration, transition-delay, and transition-timing-function and the transition shorthand for animations that are not using @keyframes.

Triggering an Animation

The animations package required specifying triggers using the 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

With Native CSS

Transition and Triggers

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

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

You can use css-grid to animate to auto height.

With Native CSS

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 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

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

Here's how the same thing can be accomplished without the animations package using animate.enter.

With Native CSS

Use animate.leave to animate elements as they leave the view, which will apply the specified CSS classes to the element as it leaves the view.

With Native CSS

For more information on animate.enter and animate.leave, see the Enter and Leave animations guide.

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

With Native CSS

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.

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.

  1. Use the prefers-reduced-motion media query to ensure no animations play for users that prefer less animation.

  2. Prevent adding animation classes programatically

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.

OnAnimationStart
OnAnimationEnd
OnAnimationIteration
OnAnimationCancel

OnTransitionStart
OnTransitionRun
OnTransitionEnd
OnTransitionCancel

The Web Animations API has a lot of additional functionality. Take a look at the documentation to see all the available animation APIs.

NOTE: Be aware of bubbling issues with these callbacks. If you are animating children and parents, the events bubble up from children to parents. Consider stopping propagation or looking at more details within the event to determine if you're responding to the desired event target rather than an event bubbling up from a child node. You can examine the animationname property or the properties being transitioned to verify you have the right nodes.

Complex Sequences

The animations package has built-in functionality for creating complex sequences. These sequences are all entirely possible without the animations package.

Targeting specific elements

In the animations package, you could target specific elements by using the query() function to find specific elements by a CSS class name, similar to document.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()

The 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

With Native CSS

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

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<

With Native CSS

## Migrating usages of AnimationPlayer

The 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(). This returns an array of every Animation on that element. You can use the Animation API to do much more than you could with what the 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

You can use view transitions to animate between routes. See the Route Transition Animations Guide to get started.# Angular without ZoneJS (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 in startup time cost.
  • 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 cannot be patched effectively, such as async/await, and have to 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

// standalone bootstrap
bootstrapApplication(MyApp, {providers: [
  provideZonelessChangeDetection(),
]});

// NgModule bootstrap
platformBrowser().bootstrapModule(AppModule);
@NgModule({
  providers: [provideZonelessChangeDetection()]
})
export class AppModule {}

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

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 (called automatically by AsyncPipe)
  • 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

One way to ensure that a component is using the correct notification mechanisms from above is to use ChangeDetectionStrategy.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. When a library component is a host for user-components which might use ChangeDetectionStrategy.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, etc.). Being a host for a user component means using an API such as ViewContainerRef.createComponent and not just hosting a portion of a template from a user component (i.e. content projection or a using a template ref input).

Remove NgZone.onMicrotaskEmpty, NgZone.onUnstable, NgZone.isStable, or NgZone.onStable

Applications and libraries need to remove uses of NgZone.onMicrotaskEmpty, NgZone.onUnstable and NgZone.onStable. These observables will never emit when an Application enables zoneless change detection. Similarly, NgZone.isStable will always be true and should not be used as a condition for code execution.

The NgZone.onMicrotaskEmpty and 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 if they need to wait for a single change detection or 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 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)

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 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 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 request.

Testing and Debugging

Using Zoneless in TestBed

The zoneless provider function can also be used with TestBed to help ensure the components under test are compatible with a Zoneless Angular application.

TestBed.configureTestingModule({
  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 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(). If the component is only used as a test wrapper and never used in an application, it is acceptable to use fixture.changeDetectorRef.markForCheck().

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. 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 two goals for the framework:

  1. Improve the Angular developer experience and
  2. Improve the framework’s performance.

Continue reading to learn how we plan to deliver these objectives with specific project work.

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

Production ready

Improving the Angular developer experience

Developer velocity

Improve Angular Material and the CDK

Improve tooling

Future work, explorations, and prototyping

This section represents explorations and prototyping of potential future projects. A reasonable outcome is to decide that our current solutions are the best options. Other projects may result in RFCs, graduating to in-progress projects, or being deprioritized as the web continues to innovate along with our framework.

Completed projects

Keeping your Angular projects up-to-date

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.

HELPFUL: If you are currently using AngularJS, see Upgrading from AngularJS. AngularJS is the name for all v1.x versions of Angular.

Getting notified of new releases

To be notified when new releases are available, follow @angular on X (formerly Twitter) or subscribe to the Angular blog.

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.

To review a complete list of changes, organized by version, see the Angular change log.

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

The most recent stable released version of Angular appears 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. By default, ng update(without additional arguments) lists the updates that are available to you.

Updating your environment and apps

To make updating uncomplicated, we provide complete instructions in the interactive Angular 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 is all you need. Without additional arguments, ng 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 describes the level of change that you can expect based on a release's version number. It also describes supported update paths.

Resource summary

HttpClient security

HttpClient includes built-in support for two common HTTP security mechanisms: XSSI protection and XSRF/CSRF protection.

TIP: Also consider adopting a Content Security Policy for your APIs.

XSSI protection

Cross-Site Script Inclusion (XSSI) is a form of Cross-Site Scripting attack where an attacker loads JSON data from your API endpoints as <script>s on a page they control. Different JavaScript techniques can then be used to access this data.

A common technique to prevent XSSI is to serve JSON responses with a "non-executable prefix", commonly )]}',\n. This prefix prevents the JSON response from being interpreted as valid executable JavaScript. When the API is loaded as data, the prefix can be stripped before JSON parsing.

HttpClient automatically strips this XSSI prefix (if present) when parsing JSON from a response.

XSRF/CSRF protection

Cross-Site Request Forgery (XSRF or CSRF) is an attack technique by which the attacker can trick an authenticated user into unknowingly executing actions on your website.

HttpClient supports a common mechanism 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 URLs, but not on GET/HEAD requests or on requests with an absolute URL. 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 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. 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

If your backend service uses different names for the XSRF token cookie or header, use withXsrfConfiguration to override the defaults.

Add it to the provideHttpClient call as follows:

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withXsrfConfiguration({
        cookieName: 'CUSTOM_XSRF_TOKEN',
        headerName: 'X-Custom-Xsrf-Header',
      }),
    ),
  ]
};

Disabling XSRF protection

If the built-in XSRF protection mechanism doesn't work for your application, you can disable it using the withNoXsrfProtection feature:

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withNoXsrfProtection(),
    ),
  ]
};

Angular Internationalization (i18n)

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:

Learn about Angular internationalization

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment